1 /* 2 * Copyright (C) 2015 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 17 package com.android.tv; 18 19 import android.app.Activity; 20 import android.content.ActivityNotFoundException; 21 import android.content.BroadcastReceiver; 22 import android.content.ComponentName; 23 import android.content.ContentUris; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.PackageManager; 28 import android.content.res.Configuration; 29 import android.database.Cursor; 30 import android.graphics.Bitmap; 31 import android.graphics.BitmapFactory; 32 import android.graphics.Point; 33 import android.hardware.display.DisplayManager; 34 import android.media.AudioManager; 35 import android.media.MediaMetadata; 36 import android.media.session.MediaSession; 37 import android.media.session.PlaybackState; 38 import android.media.tv.TvContentRating; 39 import android.media.tv.TvContract; 40 import android.media.tv.TvContract.Channels; 41 import android.media.tv.TvInputInfo; 42 import android.media.tv.TvInputManager; 43 import android.media.tv.TvInputManager.TvInputCallback; 44 import android.media.tv.TvTrackInfo; 45 import android.media.tv.TvView.OnUnhandledInputEventListener; 46 import android.net.Uri; 47 import android.os.AsyncTask; 48 import android.os.Build; 49 import android.os.Bundle; 50 import android.os.Handler; 51 import android.os.Message; 52 import android.os.PowerManager; 53 import android.provider.Settings; 54 import android.support.annotation.IntDef; 55 import android.support.annotation.NonNull; 56 import android.support.annotation.Nullable; 57 import android.text.TextUtils; 58 import android.util.ArraySet; 59 import android.util.Log; 60 import android.view.Display; 61 import android.view.Gravity; 62 import android.view.InputEvent; 63 import android.view.KeyEvent; 64 import android.view.View; 65 import android.view.ViewGroup; 66 import android.view.ViewTreeObserver; 67 import android.view.Window; 68 import android.view.WindowManager; 69 import android.view.accessibility.AccessibilityEvent; 70 import android.view.accessibility.AccessibilityManager; 71 import android.widget.FrameLayout; 72 import android.widget.Toast; 73 74 import com.android.tv.analytics.DurationTimer; 75 import com.android.tv.analytics.SendChannelStatusRunnable; 76 import com.android.tv.analytics.SendConfigInfoRunnable; 77 import com.android.tv.analytics.Tracker; 78 import com.android.tv.common.BuildConfig; 79 import com.android.tv.common.MemoryManageable; 80 import com.android.tv.common.SoftPreconditions; 81 import com.android.tv.common.TvCommonUtils; 82 import com.android.tv.common.TvContentRatingCache; 83 import com.android.tv.common.WeakHandler; 84 import com.android.tv.common.feature.CommonFeatures; 85 import com.android.tv.common.ui.setup.OnActionClickListener; 86 import com.android.tv.data.Channel; 87 import com.android.tv.data.ChannelDataManager; 88 import com.android.tv.data.OnCurrentProgramUpdatedListener; 89 import com.android.tv.data.Program; 90 import com.android.tv.data.ProgramDataManager; 91 import com.android.tv.data.StreamInfo; 92 import com.android.tv.data.WatchedHistoryManager; 93 import com.android.tv.data.epg.EpgFetcher; 94 import com.android.tv.dialog.PinDialogFragment; 95 import com.android.tv.dialog.SafeDismissDialogFragment; 96 import com.android.tv.dvr.ConflictChecker; 97 import com.android.tv.dvr.DvrManager; 98 import com.android.tv.dvr.DvrUiHelper; 99 import com.android.tv.dvr.ScheduledRecording; 100 import com.android.tv.dvr.ui.DvrStopRecordingFragment; 101 import com.android.tv.dvr.ui.HalfSizedDialogFragment; 102 import com.android.tv.experiments.Experiments; 103 import com.android.tv.menu.Menu; 104 import com.android.tv.onboarding.OnboardingActivity; 105 import com.android.tv.parental.ContentRatingsManager; 106 import com.android.tv.parental.ParentalControlSettings; 107 import com.android.tv.receiver.AudioCapabilitiesReceiver; 108 import com.android.tv.recommendation.NotificationService; 109 import com.android.tv.search.ProgramGuideSearchFragment; 110 import com.android.tv.tuner.TunerPreferences; 111 import com.android.tv.tuner.setup.TunerSetupActivity; 112 import com.android.tv.tuner.tvinput.TunerTvInputService; 113 import com.android.tv.ui.AppLayerTvView; 114 import com.android.tv.ui.ChannelBannerView; 115 import com.android.tv.ui.InputBannerView; 116 import com.android.tv.ui.KeypadChannelSwitchView; 117 import com.android.tv.ui.SelectInputView; 118 import com.android.tv.ui.SelectInputView.OnInputSelectedCallback; 119 import com.android.tv.ui.TunableTvView; 120 import com.android.tv.ui.TunableTvView.BlockScreenType; 121 import com.android.tv.ui.TunableTvView.OnTuneListener; 122 import com.android.tv.ui.TvOverlayManager; 123 import com.android.tv.ui.TvViewUiManager; 124 import com.android.tv.ui.sidepanel.ClosedCaptionFragment; 125 import com.android.tv.ui.sidepanel.CustomizeChannelListFragment; 126 import com.android.tv.ui.sidepanel.DeveloperOptionFragment; 127 import com.android.tv.ui.sidepanel.DisplayModeFragment; 128 import com.android.tv.ui.sidepanel.MultiAudioFragment; 129 import com.android.tv.ui.sidepanel.SettingsFragment; 130 import com.android.tv.ui.sidepanel.SideFragment; 131 import com.android.tv.util.AccountHelper; 132 import com.android.tv.util.CaptionSettings; 133 import com.android.tv.util.ImageCache; 134 import com.android.tv.util.ImageLoader; 135 import com.android.tv.util.OnboardingUtils; 136 import com.android.tv.util.PermissionUtils; 137 import com.android.tv.util.PipInputManager; 138 import com.android.tv.util.PipInputManager.PipInput; 139 import com.android.tv.util.RecurringRunner; 140 import com.android.tv.util.SearchManagerHelper; 141 import com.android.tv.util.SetupUtils; 142 import com.android.tv.util.SystemProperties; 143 import com.android.tv.util.TvInputManagerHelper; 144 import com.android.tv.util.TvSettings; 145 import com.android.tv.util.TvSettings.PipSound; 146 import com.android.tv.util.TvTrackInfoUtils; 147 import com.android.tv.util.Utils; 148 149 import java.lang.annotation.Retention; 150 import java.lang.annotation.RetentionPolicy; 151 import java.util.ArrayDeque; 152 import java.util.ArrayList; 153 import java.util.HashSet; 154 import java.util.List; 155 import java.util.Objects; 156 import java.util.Set; 157 import java.util.concurrent.TimeUnit; 158 159 /** 160 * The main activity for the Live TV app. 161 */ 162 public class MainActivity extends Activity implements AudioManager.OnAudioFocusChangeListener, 163 OnActionClickListener { 164 private static final String TAG = "MainActivity"; 165 private static final boolean DEBUG = false; 166 167 @Retention(RetentionPolicy.SOURCE) 168 @IntDef({KEY_EVENT_HANDLER_RESULT_PASSTHROUGH, KEY_EVENT_HANDLER_RESULT_NOT_HANDLED, 169 KEY_EVENT_HANDLER_RESULT_HANDLED, KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY}) 170 public @interface KeyHandlerResultType {} 171 public static final int KEY_EVENT_HANDLER_RESULT_PASSTHROUGH = 0; 172 public static final int KEY_EVENT_HANDLER_RESULT_NOT_HANDLED = 1; 173 public static final int KEY_EVENT_HANDLER_RESULT_HANDLED = 2; 174 public static final int KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY = 3; 175 176 private static final boolean USE_BACK_KEY_LONG_PRESS = false; 177 178 private static final float AUDIO_MAX_VOLUME = 1.0f; 179 private static final float AUDIO_MIN_VOLUME = 0.0f; 180 private static final float AUDIO_DUCKING_VOLUME = 0.3f; 181 private static final float FRAME_RATE_FOR_FILM = 23.976f; 182 private static final float FRAME_RATE_EPSILON = 0.1f; 183 184 private static final float MEDIA_SESSION_STOPPED_SPEED = 0.0f; 185 private static final float MEDIA_SESSION_PLAYING_SPEED = 1.0f; 186 187 188 private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1; 189 private static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 2; 190 private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS"; 191 192 // Tracker screen names. 193 public static final String SCREEN_NAME = "Main"; 194 private static final String SCREEN_BEHIND_NAME = "Behind"; 195 196 private static final float REFRESH_RATE_EPSILON = 0.01f; 197 private static final HashSet<Integer> BLACKLIST_KEYCODE_TO_TIS; 198 // These keys won't be passed to TIS in addition to gamepad buttons. 199 static { 200 BLACKLIST_KEYCODE_TO_TIS = new HashSet<>(); 201 BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_TV_INPUT); 202 BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MENU); 203 BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_UP); 204 BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_DOWN); 205 BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_UP); 206 BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_DOWN); 207 BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_MUTE); 208 BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MUTE); 209 BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_SEARCH); 210 BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_WINDOW); 211 } 212 213 214 private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1; 215 private static final int REQUEST_CODE_START_SYSTEM_CAPTIONING_SETTINGS = 2; 216 217 private static final String KEY_INIT_CHANNEL_ID = "com.android.tv.init_channel_id"; 218 219 private static final String MEDIA_SESSION_TAG = "com.android.tv.mediasession"; 220 221 // Change channels with key long press. 222 private static final int CHANNEL_CHANGE_NORMAL_SPEED_DURATION_MS = 3000; 223 private static final int CHANNEL_CHANGE_DELAY_MS_IN_MAX_SPEED = 50; 224 private static final int CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED = 200; 225 private static final int CHANNEL_CHANGE_INITIAL_DELAY_MILLIS = 500; 226 private static final int FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS = 500; 227 228 private static final int MSG_CHANNEL_DOWN_PRESSED = 1000; 229 private static final int MSG_CHANNEL_UP_PRESSED = 1001; 230 private static final int MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE = 1002; 231 232 @Retention(RetentionPolicy.SOURCE) 233 @IntDef({UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW, UPDATE_CHANNEL_BANNER_REASON_TUNE, 234 UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST, UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO, 235 UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK}) 236 private @interface ChannelBannerUpdateReason {} 237 /** 238 * Updates channel banner because the channel banner is forced to show. 239 */ 240 private static final int UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW = 1; 241 /** 242 * Updates channel banner because of tuning. 243 */ 244 private static final int UPDATE_CHANNEL_BANNER_REASON_TUNE = 2; 245 /** 246 * Updates channel banner because of fast tuning. 247 */ 248 private static final int UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST = 3; 249 /** 250 * Updates channel banner because of info updating. 251 */ 252 private static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO = 4; 253 /** 254 * Updates channel banner because the current watched channel is locked or unlocked. 255 */ 256 private static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5; 257 258 private static final int TVVIEW_SET_MAIN_TIMEOUT_MS = 3000; 259 260 // Lazy initialization. 261 // Delay 1 second in order not to interrupt the first tune. 262 private static final long LAZY_INITIALIZATION_DELAY = TimeUnit.SECONDS.toMillis(1); 263 264 private AccessibilityManager mAccessibilityManager; 265 private ChannelDataManager mChannelDataManager; 266 private ProgramDataManager mProgramDataManager; 267 private TvInputManagerHelper mTvInputManagerHelper; 268 private ChannelTuner mChannelTuner; 269 private PipInputManager mPipInputManager; 270 private final TvOptionsManager mTvOptionsManager = new TvOptionsManager(this); 271 private TvViewUiManager mTvViewUiManager; 272 private TimeShiftManager mTimeShiftManager; 273 private Tracker mTracker; 274 private final DurationTimer mMainDurationTimer = new DurationTimer(); 275 private final DurationTimer mTuneDurationTimer = new DurationTimer(); 276 private DvrManager mDvrManager; 277 private ConflictChecker mDvrConflictChecker; 278 279 private View mContentView; 280 private TunableTvView mTvView; 281 private TunableTvView mPipView; 282 private Bundle mTuneParams; 283 private boolean mChannelBannerHiddenBySideFragment; 284 // TODO: Move the scene views into TvTransitionManager or TvOverlayManager. 285 private ChannelBannerView mChannelBannerView; 286 private KeypadChannelSwitchView mKeypadChannelSwitchView; 287 @Nullable 288 private Uri mInitChannelUri; 289 @Nullable 290 private String mParentInputIdWhenScreenOff; 291 private boolean mScreenOffIntentReceived; 292 private boolean mShowProgramGuide; 293 private boolean mShowSelectInputView; 294 private TvInputInfo mInputToSetUp; 295 private final List<MemoryManageable> mMemoryManageables = new ArrayList<>(); 296 private MediaSession mMediaSession; 297 private int mNowPlayingCardWidth; 298 private int mNowPlayingCardHeight; 299 private final MyOnTuneListener mOnTuneListener = new MyOnTuneListener(); 300 301 private String mInputIdUnderSetup; 302 private boolean mIsSetupActivityCalledByPopup; 303 private AudioManager mAudioManager; 304 private int mAudioFocusStatus; 305 private boolean mTunePending; 306 private boolean mPipEnabled; 307 private Channel mPipChannel; 308 private boolean mPipSwap; 309 @PipSound private int mPipSound = TvSettings.PIP_SOUND_MAIN; // Default 310 private boolean mDebugNonFullSizeScreen; 311 private boolean mActivityResumed; 312 private boolean mActivityStarted; 313 private boolean mShouldTuneToTunerChannel; 314 private boolean mUseKeycodeBlacklist; 315 private boolean mShowLockedChannelsTemporarily; 316 private boolean mBackKeyPressed; 317 private boolean mNeedShowBackKeyGuide; 318 private boolean mVisibleBehind; 319 private boolean mAc3PassthroughSupported; 320 private boolean mShowNewSourcesFragment = true; 321 private String mTunerInputId; 322 private boolean mOtherActivityLaunched; 323 324 private boolean mIsFilmModeSet; 325 private float mDefaultRefreshRate; 326 327 private TvOverlayManager mOverlayManager; 328 329 // mIsCurrentChannelUnblockedByUser and mWasChannelUnblockedBeforeShrunkenByUser are used for 330 // keeping the channel unblocking status while TV view is shrunken. 331 private boolean mIsCurrentChannelUnblockedByUser; 332 private boolean mWasChannelUnblockedBeforeShrunkenByUser; 333 private Channel mChannelBeforeShrunkenTvView; 334 private Channel mPipChannelBeforeShrunkenTvView; 335 private boolean mIsCompletingShrunkenTvView; 336 337 // TODO: Need to consider the case that TIS explicitly request PIN code while TV view is 338 // shrunken. 339 private TvContentRating mLastAllowedRatingForCurrentChannel; 340 private TvContentRating mAllowedRatingBeforeShrunken; 341 342 private CaptionSettings mCaptionSettings; 343 // Lazy initialization 344 private boolean mLazyInitialized; 345 346 private static final int MAX_RECENT_CHANNELS = 5; 347 private final ArrayDeque<Long> mRecentChannels = new ArrayDeque<>(MAX_RECENT_CHANNELS); 348 349 private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver; 350 private RecurringRunner mSendConfigInfoRecurringRunner; 351 private RecurringRunner mChannelStatusRecurringRunner; 352 353 // A caller which started this activity. (e.g. TvSearch) 354 private String mSource; 355 356 private final Handler mHandler = new MainActivityHandler(this); 357 private final Set<OnActionClickListener> mOnActionClickListeners = new ArraySet<>(); 358 359 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 360 @Override 361 public void onReceive(Context context, Intent intent) { 362 if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { 363 if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF"); 364 // We need to stop TvView, when the screen is turned off. If not and TIS uses 365 // MediaPlayer, a device may not go to the sleep mode and audio can be heard, 366 // because MediaPlayer keeps playing media by its wake lock. 367 mScreenOffIntentReceived = true; 368 markCurrentChannelDuringScreenOff(); 369 stopAll(true); 370 } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { 371 if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON"); 372 if (!mActivityResumed && mVisibleBehind) { 373 // ACTION_SCREEN_ON is usually called after onResume. But, if media is played 374 // under launcher with requestVisibleBehind(true), onResume will not be called. 375 // In this case, we need to resume TvView and PipView explicitly. 376 resumeTvIfNeeded(); 377 resumePipIfNeeded(); 378 } 379 } else if (intent.getAction().equals( 380 TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED)) { 381 if (DEBUG) Log.d(TAG, "Received parental control settings change"); 382 checkChannelLockNeeded(mTvView); 383 checkChannelLockNeeded(mPipView); 384 applyParentalControlSettings(); 385 } 386 } 387 }; 388 389 private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener = 390 new OnCurrentProgramUpdatedListener() { 391 @Override 392 public void onCurrentProgramUpdated(long channelId, Program program) { 393 // Do not update channel banner by this notification 394 // when the time shifting is available. 395 if (mTimeShiftManager.isAvailable()) { 396 return; 397 } 398 Channel channel = mTvView.getCurrentChannel(); 399 if (channel != null && channel.getId() == channelId) { 400 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); 401 updateMediaSession(); 402 } 403 } 404 }; 405 406 private final ChannelTuner.Listener mChannelTunerListener = 407 new ChannelTuner.Listener() { 408 @Override 409 public void onLoadFinished() { 410 SetupUtils.getInstance(MainActivity.this).markNewChannelsBrowsable(); 411 if (mActivityResumed) { 412 resumeTvIfNeeded(); 413 resumePipIfNeeded(); 414 } 415 mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList()); 416 } 417 418 @Override 419 public void onBrowsableChannelListChanged() { 420 mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList()); 421 } 422 423 @Override 424 public void onCurrentChannelUnavailable(Channel channel) { 425 // TODO: handle the case that a channel is suddenly removed from DB. 426 } 427 428 @Override 429 public void onChannelChanged(Channel previousChannel, Channel currentChannel) { 430 } 431 }; 432 433 private final Runnable mRestoreMainViewRunnable = 434 new Runnable() { 435 @Override 436 public void run() { 437 restoreMainTvView(); 438 } 439 }; 440 private ProgramGuideSearchFragment mSearchFragment; 441 442 private final TvInputCallback mTvInputCallback = new TvInputCallback() { 443 @Override 444 public void onInputAdded(String inputId) { 445 if (Features.TUNER.isEnabled(MainActivity.this) && mTunerInputId.equals(inputId) 446 && TunerPreferences.shouldShowSetupActivity(MainActivity.this)) { 447 Intent intent = TunerSetupActivity.createSetupActivity(MainActivity.this); 448 startActivity(intent); 449 TunerPreferences.setShouldShowSetupActivity(MainActivity.this, false); 450 SetupUtils.getInstance(MainActivity.this).markAsKnownInput(mTunerInputId); 451 } 452 } 453 }; 454 applyParentalControlSettings()455 private void applyParentalControlSettings() { 456 boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings() 457 .isParentalControlsEnabled(); 458 mTvView.onParentalControlChanged(parentalControlEnabled); 459 mPipView.onParentalControlChanged(parentalControlEnabled); 460 } 461 462 @Override onCreate(Bundle savedInstanceState)463 protected void onCreate(Bundle savedInstanceState) { 464 if (DEBUG) Log.d(TAG,"onCreate()"); 465 TvApplication.setCurrentRunningProcess(this, true); 466 super.onCreate(savedInstanceState); 467 468 boolean isPassthroughInput = TvContract.isChannelUriForPassthroughInput(getIntent() 469 .getData()); 470 boolean skipToShowOnboarding = Intent.ACTION_VIEW.equals(getIntent().getAction()) 471 && isPassthroughInput; 472 if (OnboardingUtils.needToShowOnboarding(this) && !skipToShowOnboarding 473 && !TvCommonUtils.isRunningInTest()) { 474 // TODO: The onboarding is turned off in test, because tests are broken by the 475 // onboarding. We need to enable the feature for tests later. 476 startActivity(OnboardingActivity.buildIntent(this, getIntent())); 477 finish(); 478 return; 479 } 480 481 // Check this permission for the EPG fetch. 482 // TODO: check {@link shouldShowRequestPermissionRationale}. 483 // While testing, no way to allow the permission when the dialog shows up. So we'd better 484 // not show the dialog. 485 if (!TvCommonUtils.isRunningInTest() && Utils.hasInternalTvInputs(this, true) 486 && checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) 487 != PackageManager.PERMISSION_GRANTED) { 488 requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION}, 489 PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION); 490 } 491 492 DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); 493 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 494 Point size = new Point(); 495 display.getSize(size); 496 int screenWidth = size.x; 497 int screenHeight = size.y; 498 mDefaultRefreshRate = display.getRefreshRate(); 499 500 setContentView(R.layout.activity_tv); 501 mContentView = findViewById(android.R.id.content); 502 mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view); 503 int shrunkenTvViewHeight = getResources().getDimensionPixelSize( 504 R.dimen.shrunken_tvview_height); 505 mTvView.initialize((AppLayerTvView) findViewById(R.id.main_tv_view), false, screenHeight, 506 shrunkenTvViewHeight); 507 mTvView.setOnUnhandledInputEventListener(new OnUnhandledInputEventListener() { 508 @Override 509 public boolean onUnhandledInputEvent(InputEvent event) { 510 if (isKeyEventBlocked()) { 511 return true; 512 } 513 if (event instanceof KeyEvent) { 514 KeyEvent keyEvent = (KeyEvent) event; 515 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.isLongPress()) { 516 if (onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) { 517 return true; 518 } 519 } 520 if (keyEvent.getAction() == KeyEvent.ACTION_UP) { 521 return onKeyUp(keyEvent.getKeyCode(), keyEvent); 522 } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { 523 return onKeyDown(keyEvent.getKeyCode(), keyEvent); 524 } 525 } 526 return false; 527 } 528 }); 529 530 long channelId = Utils.getLastWatchedChannelId(this); 531 String inputId = Utils.getLastWatchedTunerInputId(this); 532 if (!isPassthroughInput && inputId != null 533 && channelId != Channel.INVALID_ID) { 534 mTvView.warmUpInput(inputId, TvContract.buildChannelUri(channelId)); 535 } 536 537 TvApplication tvApplication = (TvApplication) getApplication(); 538 tvApplication.getMainActivityWrapper().onMainActivityCreated(this); 539 if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) { 540 Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show(); 541 } 542 mTracker = tvApplication.getTracker(); 543 mTvInputManagerHelper = tvApplication.getTvInputManagerHelper(); 544 if (Features.TUNER.isEnabled(this)) { 545 mTvInputManagerHelper.addCallback(mTvInputCallback); 546 } 547 mTunerInputId = TunerTvInputService.getInputId(this); 548 mChannelDataManager = tvApplication.getChannelDataManager(); 549 mProgramDataManager = tvApplication.getProgramDataManager(); 550 mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID, 551 mOnCurrentProgramUpdatedListener); 552 mProgramDataManager.setPrefetchEnabled(true); 553 mChannelTuner = new ChannelTuner(mChannelDataManager, mTvInputManagerHelper); 554 mChannelTuner.addListener(mChannelTunerListener); 555 mChannelTuner.start(); 556 mPipInputManager = new PipInputManager(this, mTvInputManagerHelper, mChannelTuner); 557 mPipInputManager.start(); 558 mMemoryManageables.add(mProgramDataManager); 559 mMemoryManageables.add(ImageCache.getInstance()); 560 mMemoryManageables.add(TvContentRatingCache.getInstance()); 561 if (CommonFeatures.DVR.isEnabled(this)) { 562 mDvrManager = tvApplication.getDvrManager(); 563 } 564 mTimeShiftManager = new TimeShiftManager(this, mTvView, mProgramDataManager, mTracker, 565 new OnCurrentProgramUpdatedListener() { 566 @Override 567 public void onCurrentProgramUpdated(long channelId, Program program) { 568 updateMediaSession(); 569 switch (mTimeShiftManager.getLastActionId()) { 570 case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND: 571 case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD: 572 case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS: 573 case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT: 574 updateChannelBannerAndShowIfNeeded( 575 UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); 576 break; 577 case TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE: 578 case TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY: 579 default: 580 updateChannelBannerAndShowIfNeeded( 581 UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); 582 break; 583 } 584 } 585 }); 586 587 mPipView = (TunableTvView) findViewById(R.id.pip_tunable_tv_view); 588 mPipView.initialize((AppLayerTvView) findViewById(R.id.pip_tv_view), true, screenHeight, 589 shrunkenTvViewHeight); 590 591 if (!PermissionUtils.hasAccessWatchedHistory(this)) { 592 WatchedHistoryManager watchedHistoryManager = new WatchedHistoryManager( 593 getApplicationContext()); 594 watchedHistoryManager.start(); 595 mTvView.setWatchedHistoryManager(watchedHistoryManager); 596 } 597 mTvViewUiManager = new TvViewUiManager(this, mTvView, mPipView, 598 (FrameLayout) findViewById(android.R.id.content), mTvOptionsManager); 599 600 mPipView.setFixedSurfaceSize(screenWidth / 2, screenHeight / 2); 601 mPipView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW); 602 603 ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container); 604 mChannelBannerView = (ChannelBannerView) getLayoutInflater().inflate( 605 R.layout.channel_banner, sceneContainer, false); 606 mKeypadChannelSwitchView = (KeypadChannelSwitchView) getLayoutInflater().inflate( 607 R.layout.keypad_channel_switch, sceneContainer, false); 608 InputBannerView inputBannerView = (InputBannerView) getLayoutInflater() 609 .inflate(R.layout.input_banner, sceneContainer, false); 610 SelectInputView selectInputView = (SelectInputView) getLayoutInflater() 611 .inflate(R.layout.select_input, sceneContainer, false); 612 selectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() { 613 @Override 614 public void onTunerInputSelected() { 615 Channel currentChannel = mChannelTuner.getCurrentChannel(); 616 if (currentChannel != null && !currentChannel.isPassthrough()) { 617 hideOverlays(); 618 } else { 619 tuneToLastWatchedChannelForTunerInput(); 620 } 621 } 622 623 @Override 624 public void onPassthroughInputSelected(@NonNull TvInputInfo input) { 625 Channel currentChannel = mChannelTuner.getCurrentChannel(); 626 String currentInputId = currentChannel == null ? null : currentChannel.getInputId(); 627 if (TextUtils.equals(input.getId(), currentInputId)) { 628 hideOverlays(); 629 } else { 630 tuneToChannel(Channel.createPassthroughChannel(input.getId())); 631 } 632 } 633 634 private void hideOverlays() { 635 getOverlayManager().hideOverlays( 636 TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG 637 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS 638 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE 639 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU 640 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); 641 } 642 }); 643 mSearchFragment = new ProgramGuideSearchFragment(); 644 mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTvView, 645 mKeypadChannelSwitchView, mChannelBannerView, inputBannerView, 646 selectInputView, sceneContainer, mSearchFragment); 647 648 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 649 mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; 650 651 mMediaSession = new MediaSession(this, MEDIA_SESSION_TAG); 652 mMediaSession.setCallback(new MediaSession.Callback() { 653 @Override 654 public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { 655 // Consume the media button event here. Should not send it to other apps. 656 return true; 657 } 658 }); 659 mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | 660 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 661 mNowPlayingCardWidth = getResources().getDimensionPixelSize( 662 R.dimen.notif_card_img_max_width); 663 mNowPlayingCardHeight = getResources().getDimensionPixelSize(R.dimen.notif_card_img_height); 664 665 mTvViewUiManager.restoreDisplayMode(false); 666 if (!handleIntent(getIntent())) { 667 finish(); 668 return; 669 } 670 671 mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this, 672 new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() { 673 @Override 674 public void onAc3PassthroughCapabilityChange(boolean capability) { 675 mAc3PassthroughSupported = capability; 676 } 677 }); 678 mAudioCapabilitiesReceiver.register(); 679 680 mAccessibilityManager = 681 (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE); 682 mSendConfigInfoRecurringRunner = new RecurringRunner(this, TimeUnit.DAYS.toMillis(1), 683 new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper), null); 684 mSendConfigInfoRecurringRunner.start(); 685 mChannelStatusRecurringRunner = SendChannelStatusRunnable 686 .startChannelStatusRecurringRunner(this, mTracker, mChannelDataManager); 687 688 // To avoid not updating Rating systems when changing language. 689 mTvInputManagerHelper.getContentRatingsManager().update(); 690 if (CommonFeatures.DVR.isEnabled(this) 691 && Features.SHOW_UPCOMING_CONFLICT_DIALOG.isEnabled(this)) { 692 mDvrConflictChecker = new ConflictChecker(this); 693 } 694 initForTest(); 695 } 696 697 @Override onConfigurationChanged(Configuration newConfig)698 public void onConfigurationChanged(Configuration newConfig) { 699 super.onConfigurationChanged(newConfig); 700 float density = getResources().getDisplayMetrics().density; 701 mTvViewUiManager.onConfigurationChanged((int) (newConfig.screenWidthDp * density), 702 (int) (newConfig.screenHeightDp * density)); 703 } 704 705 @Override onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)706 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 707 @NonNull int[] grantResults) { 708 switch (requestCode) { 709 case PERMISSIONS_REQUEST_READ_TV_LISTINGS: 710 if (grantResults.length > 0 711 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 712 // Start reload of dependent data 713 mChannelDataManager.reload(); 714 mProgramDataManager.reload(); 715 716 // Restart live channels. 717 Intent intent = getIntent(); 718 finish(); 719 startActivity(intent); 720 } else { 721 Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied, 722 Toast.LENGTH_LONG).show(); 723 finish(); 724 } 725 break; 726 case PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION: 727 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED 728 && Experiments.CLOUD_EPG.get()) { 729 EpgFetcher.getInstance(this).startImmediately(); 730 } else { 731 EpgFetcher.getInstance(this).stop(); 732 } 733 break; 734 } 735 } 736 getDesiredBlockScreenType()737 @BlockScreenType private int getDesiredBlockScreenType() { 738 if (!mActivityResumed) { 739 return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI; 740 } 741 if (isUnderShrunkenTvView()) { 742 return TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW; 743 } 744 if (mOverlayManager.needHideTextOnMainView()) { 745 return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI; 746 } 747 SafeDismissDialogFragment currentDialog = mOverlayManager.getCurrentDialog(); 748 if (currentDialog != null) { 749 // If PIN dialog is shown for unblocking the channel lock or content ratings lock, 750 // keeping the unlocking message is more natural instead of changing it. 751 if (currentDialog instanceof PinDialogFragment) { 752 int type = ((PinDialogFragment) currentDialog).getType(); 753 if (type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL 754 || type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) { 755 return TunableTvView.BLOCK_SCREEN_TYPE_NORMAL; 756 } 757 } 758 return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI; 759 } 760 if (mOverlayManager.isSetupFragmentActive() 761 || mOverlayManager.isNewSourcesFragmentActive()) { 762 return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI; 763 } 764 return TunableTvView.BLOCK_SCREEN_TYPE_NORMAL; 765 } 766 767 @Override onNewIntent(Intent intent)768 protected void onNewIntent(Intent intent) { 769 if (DEBUG) Log.d(TAG,"onNewIntent(): " + intent); 770 if (mOverlayManager == null) { 771 // It's called before onCreate. The intent will be handled at onCreate. b/30725058 772 return; 773 } 774 mOverlayManager.getSideFragmentManager().hideAll(false); 775 if (!handleIntent(intent) && !mActivityStarted) { 776 // If the activity is stopped and not destroyed, finish the activity. 777 // Otherwise, just ignore the intent. 778 finish(); 779 } 780 } 781 782 @Override onStart()783 protected void onStart() { 784 if (DEBUG) Log.d(TAG,"onStart()"); 785 super.onStart(); 786 mScreenOffIntentReceived = false; 787 mActivityStarted = true; 788 mTracker.sendMainStart(); 789 mMainDurationTimer.start(); 790 791 applyParentalControlSettings(); 792 IntentFilter intentFilter = new IntentFilter(); 793 intentFilter.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED); 794 intentFilter.addAction(Intent.ACTION_SCREEN_OFF); 795 intentFilter.addAction(Intent.ACTION_SCREEN_ON); 796 registerReceiver(mBroadcastReceiver, intentFilter); 797 798 Intent notificationIntent = new Intent(this, NotificationService.class); 799 notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION); 800 startService(notificationIntent); 801 } 802 803 @Override onResume()804 protected void onResume() { 805 if (DEBUG) Log.d(TAG, "onResume()"); 806 super.onResume(); 807 // Refresh the remote config, it is throttled automatically. 808 TvApplication.getSingletons(this).getRemoteConfig().fetch(null); 809 if (!PermissionUtils.hasAccessAllEpg(this) 810 && checkSelfPermission(PERMISSION_READ_TV_LISTINGS) 811 != PackageManager.PERMISSION_GRANTED) { 812 requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS}, 813 PERMISSIONS_REQUEST_READ_TV_LISTINGS); 814 } 815 mTracker.sendScreenView(SCREEN_NAME); 816 817 SystemProperties.updateSystemProperties(); 818 mNeedShowBackKeyGuide = true; 819 mActivityResumed = true; 820 mShowNewSourcesFragment = true; 821 mOtherActivityLaunched = false; 822 int result = mAudioManager.requestAudioFocus(MainActivity.this, 823 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); 824 mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ? 825 AudioManager.AUDIOFOCUS_GAIN : AudioManager.AUDIOFOCUS_LOSS; 826 setVolumeByAudioFocusStatus(); 827 828 if (mTvView.isPlaying()) { 829 // Every time onResume() is called the activity will be assumed to not have requested 830 // visible behind. 831 requestVisibleBehind(true); 832 } 833 if (Utils.hasRecordingFailedReason(getApplicationContext(), 834 TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)) { 835 runAfterAttachedToWindow(new Runnable() { 836 @Override 837 public void run() { 838 DvrUiHelper.showDvrInsufficientSpaceErrorDialog(MainActivity.this); 839 } 840 }); 841 } 842 843 if (mChannelTuner.areAllChannelsLoaded()) { 844 SetupUtils.getInstance(this).markNewChannelsBrowsable(); 845 resumeTvIfNeeded(); 846 resumePipIfNeeded(); 847 } 848 mOverlayManager.showMenuWithTimeShiftPauseIfNeeded(); 849 850 // Note: The following codes are related to pop up an overlay UI after resume. 851 // When the following code is changed, please check the variable 852 // willShowOverlayUiAfterResume in updateChannelBannerAndShowIfNeeded. 853 if (mInputToSetUp != null) { 854 startSetupActivity(mInputToSetUp, false); 855 mInputToSetUp = null; 856 } else if (mShowProgramGuide) { 857 mShowProgramGuide = false; 858 mHandler.post(new Runnable() { 859 // This will delay the start of the animation until after the Live Channel app is 860 // shown. Without this the animation is completed before it is actually visible on 861 // the screen. 862 @Override 863 public void run() { 864 mOverlayManager.showProgramGuide(); 865 } 866 }); 867 } else if (mShowSelectInputView) { 868 mShowSelectInputView = false; 869 mHandler.post(new Runnable() { 870 // mShowSelectInputView is true when the activity is started/resumed because the 871 // TV_INPUT button was pressed in a different app. 872 // This will delay the start of the animation until after the Live Channel app is 873 // shown. Without this the animation is completed before it is actually visible on 874 // the screen. 875 @Override 876 public void run() { 877 mOverlayManager.showSelectInputView(); 878 } 879 }); 880 } 881 if (mDvrConflictChecker != null) { 882 mDvrConflictChecker.start(); 883 } 884 } 885 886 @Override onPause()887 protected void onPause() { 888 if (DEBUG) Log.d(TAG, "onPause()"); 889 if (mDvrConflictChecker != null) { 890 mDvrConflictChecker.stop(); 891 } 892 finishChannelChangeIfNeeded(); 893 mActivityResumed = false; 894 mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_DEFAULT); 895 mTvView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_NO_UI); 896 if (mPipEnabled) { 897 mTvViewUiManager.hidePipForPause(); 898 } 899 mBackKeyPressed = false; 900 mShowLockedChannelsTemporarily = false; 901 mShouldTuneToTunerChannel = false; 902 if (!mVisibleBehind) { 903 mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; 904 mAudioManager.abandonAudioFocus(this); 905 if (mMediaSession.isActive()) { 906 mMediaSession.setActive(false); 907 } 908 mTracker.sendScreenView(""); 909 } else { 910 mTracker.sendScreenView(SCREEN_BEHIND_NAME); 911 } 912 super.onPause(); 913 } 914 915 /** 916 * Returns true if {@link #onResume} is called and {@link #onPause} is not called yet. 917 */ isActivityResumed()918 public boolean isActivityResumed() { 919 return mActivityResumed; 920 } 921 922 /** 923 * Returns true if {@link #onStart} is called and {@link #onStop} is not called yet. 924 */ isActivityStarted()925 public boolean isActivityStarted() { 926 return mActivityStarted; 927 } 928 929 @Override requestVisibleBehind(boolean enable)930 public boolean requestVisibleBehind(boolean enable) { 931 boolean state = super.requestVisibleBehind(enable); 932 mVisibleBehind = state; 933 return state; 934 } 935 resumeTvIfNeeded()936 private void resumeTvIfNeeded() { 937 if (DEBUG) Log.d(TAG, "resumeTvIfNeeded()"); 938 if (!mTvView.isPlaying() || mInitChannelUri != null 939 || (mShouldTuneToTunerChannel && mChannelTuner.isCurrentChannelPassthrough())) { 940 if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) { 941 // The target input may not be ready yet, especially, just after screen on. 942 String inputId = mInitChannelUri.getPathSegments().get(1); 943 TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(inputId); 944 if (input == null) { 945 input = mTvInputManagerHelper.getTvInputInfo(mParentInputIdWhenScreenOff); 946 if (input == null) { 947 SoftPreconditions.checkState(false, TAG, "Input disappear."); 948 finish(); 949 } else { 950 mInitChannelUri = 951 TvContract.buildChannelUriForPassthroughInput(input.getId()); 952 } 953 } 954 } 955 mParentInputIdWhenScreenOff = null; 956 startTv(mInitChannelUri); 957 mInitChannelUri = null; 958 } 959 // Make sure TV app has the main TV view to handle the case that TvView is used in other 960 // application. 961 restoreMainTvView(); 962 mTvView.setBlockScreenType(getDesiredBlockScreenType()); 963 } 964 resumePipIfNeeded()965 private void resumePipIfNeeded() { 966 if (mPipEnabled && !(mPipView.isPlaying() && mPipView.isShown())) { 967 if (mPipInputManager.areInSamePipInput( 968 mChannelTuner.getCurrentChannel(), mPipChannel)) { 969 enablePipView(false, false); 970 } else { 971 if (!mPipView.isPlaying()) { 972 startPip(false); 973 } else { 974 mTvViewUiManager.showPipForResume(); 975 } 976 } 977 } 978 } 979 startTv(Uri channelUri)980 private void startTv(Uri channelUri) { 981 if (DEBUG) Log.d(TAG, "startTv Uri=" + channelUri); 982 if ((channelUri == null || !TvContract.isChannelUriForPassthroughInput(channelUri)) 983 && mChannelTuner.isCurrentChannelPassthrough()) { 984 // For passthrough TV input, channelUri is always given. If TV app is launched 985 // by TV app icon in a launcher, channelUri is null. So if passthrough TV input 986 // is playing, we stop the passthrough TV input. 987 stopTv(); 988 } 989 SoftPreconditions.checkState(TvContract.isChannelUriForPassthroughInput(channelUri) 990 || mChannelTuner.areAllChannelsLoaded(), 991 TAG, "startTV assumes that ChannelDataManager is already loaded."); 992 if (mTvView.isPlaying()) { 993 // TV has already started. 994 if (channelUri == null || channelUri.equals(mChannelTuner.getCurrentChannelUri())) { 995 // Simply adjust the volume without tune. 996 setVolumeByAudioFocusStatus(); 997 return; 998 } 999 stopTv(); 1000 } 1001 if (mChannelTuner.getCurrentChannel() != null) { 1002 Log.w(TAG, "The current channel should be reset before"); 1003 mChannelTuner.resetCurrentChannel(); 1004 } 1005 if (channelUri == null) { 1006 // If any initial channel id is not given, remember the last channel the user watched. 1007 long channelId = Utils.getLastWatchedChannelId(this); 1008 if (channelId != Channel.INVALID_ID) { 1009 channelUri = TvContract.buildChannelUri(channelId); 1010 } 1011 } 1012 1013 if (channelUri == null) { 1014 mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0)); 1015 } else { 1016 if (TvContract.isChannelUriForPassthroughInput(channelUri)) { 1017 Channel channel = Channel.createPassthroughChannel(channelUri); 1018 mChannelTuner.moveToChannel(channel); 1019 } else { 1020 long channelId = ContentUris.parseId(channelUri); 1021 Channel channel = mChannelDataManager.getChannel(channelId); 1022 if (channel == null || !mChannelTuner.moveToChannel(channel)) { 1023 mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0)); 1024 Log.w(TAG, "The requested channel (id=" + channelId + ") doesn't exist. " 1025 + "The first channel will be tuned to."); 1026 } 1027 } 1028 } 1029 1030 mTvView.start(mTvInputManagerHelper); 1031 setVolumeByAudioFocusStatus(); 1032 tune(); 1033 } 1034 1035 @Override onStop()1036 protected void onStop() { 1037 if (DEBUG) Log.d(TAG, "onStop()"); 1038 if (mScreenOffIntentReceived) { 1039 mScreenOffIntentReceived = false; 1040 } else { 1041 PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); 1042 if (!powerManager.isInteractive()) { 1043 // We added to check isInteractive as well as SCREEN_OFF intent, because 1044 // calling timing of the intent SCREEN_OFF is not consistent. b/25953633. 1045 // If we verify that checking isInteractive is enough, we can remove the logic 1046 // for SCREEN_OFF intent. 1047 markCurrentChannelDuringScreenOff(); 1048 } 1049 } 1050 mActivityStarted = false; 1051 stopAll(false); 1052 unregisterReceiver(mBroadcastReceiver); 1053 mTracker.sendMainStop(mMainDurationTimer.reset()); 1054 super.onStop(); 1055 } 1056 1057 /** 1058 * Handles screen off to keep the current channel for next screen on. 1059 */ markCurrentChannelDuringScreenOff()1060 private void markCurrentChannelDuringScreenOff() { 1061 mInitChannelUri = mChannelTuner.getCurrentChannelUri(); 1062 if (mChannelTuner.isCurrentChannelPassthrough()) { 1063 // When ACTION_SCREEN_OFF is invoked, some CEC devices may be already 1064 // removed. So we need to get the input info from ChannelTuner instead of 1065 // TvInputManagerHelper. 1066 TvInputInfo input = mChannelTuner.getCurrentInputInfo(); 1067 mParentInputIdWhenScreenOff = input.getParentId(); 1068 if (DEBUG) Log.d(TAG, "Parent input: " + mParentInputIdWhenScreenOff); 1069 } 1070 } 1071 stopAll(boolean keepVisibleBehind)1072 private void stopAll(boolean keepVisibleBehind) { 1073 mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION); 1074 stopTv("stopAll()", keepVisibleBehind); 1075 stopPip(); 1076 } 1077 getTvInputManagerHelper()1078 public TvInputManagerHelper getTvInputManagerHelper() { 1079 return mTvInputManagerHelper; 1080 } 1081 1082 /** 1083 * Starts setup activity for the given input {@code input}. 1084 * 1085 * @param calledByPopup If true, startSetupActivity is invoked from the setup fragment. 1086 */ startSetupActivity(TvInputInfo input, boolean calledByPopup)1087 public void startSetupActivity(TvInputInfo input, boolean calledByPopup) { 1088 Intent intent = TvCommonUtils.createSetupIntent(input); 1089 if (intent == null) { 1090 Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT).show(); 1091 return; 1092 } 1093 // Even though other app can handle the intent, the setup launched by Live channels 1094 // should go through Live channels SetupPassthroughActivity. 1095 intent.setComponent(new ComponentName(this, SetupPassthroughActivity.class)); 1096 try { 1097 // Now we know that the user intends to set up this input. Grant permission for writing 1098 // EPG data. 1099 SetupUtils.grantEpgPermission(this, input.getServiceInfo().packageName); 1100 1101 mInputIdUnderSetup = input.getId(); 1102 mIsSetupActivityCalledByPopup = calledByPopup; 1103 // Call requestVisibleBehind(false) before starting other activity. 1104 // In Activity.requestVisibleBehind(false), this activity is scheduled to be stopped 1105 // immediately if other activity is about to start. And this activity is scheduled to 1106 // to be stopped again after onPause(). 1107 stopTv("startSetupActivity()", false); 1108 startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY); 1109 } catch (ActivityNotFoundException e) { 1110 mInputIdUnderSetup = null; 1111 Toast.makeText(this, getString(R.string.msg_unable_to_start_setup_activity, 1112 input.loadLabel(this)), Toast.LENGTH_SHORT).show(); 1113 return; 1114 } 1115 if (calledByPopup) { 1116 mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION 1117 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); 1118 } else { 1119 mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION 1120 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY); 1121 } 1122 } 1123 hasCaptioningSettingsActivity()1124 public boolean hasCaptioningSettingsActivity() { 1125 return Utils.isIntentAvailable(this, new Intent(Settings.ACTION_CAPTIONING_SETTINGS)); 1126 } 1127 startSystemCaptioningSettingsActivity()1128 public void startSystemCaptioningSettingsActivity() { 1129 Intent intent = new Intent(Settings.ACTION_CAPTIONING_SETTINGS); 1130 mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION 1131 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY); 1132 try { 1133 startActivityForResultSafe(intent, REQUEST_CODE_START_SYSTEM_CAPTIONING_SETTINGS); 1134 } catch (ActivityNotFoundException e) { 1135 Toast.makeText(this, getString(R.string.msg_unable_to_start_system_captioning_settings), 1136 Toast.LENGTH_SHORT).show(); 1137 } 1138 } 1139 getChannelDataManager()1140 public ChannelDataManager getChannelDataManager() { 1141 return mChannelDataManager; 1142 } 1143 getProgramDataManager()1144 public ProgramDataManager getProgramDataManager() { 1145 return mProgramDataManager; 1146 } 1147 getPipInputManager()1148 public PipInputManager getPipInputManager() { 1149 return mPipInputManager; 1150 } 1151 getTvOptionsManager()1152 public TvOptionsManager getTvOptionsManager() { 1153 return mTvOptionsManager; 1154 } 1155 getTvViewUiManager()1156 public TvViewUiManager getTvViewUiManager() { 1157 return mTvViewUiManager; 1158 } 1159 getTimeShiftManager()1160 public TimeShiftManager getTimeShiftManager() { 1161 return mTimeShiftManager; 1162 } 1163 1164 /** 1165 * Returns the instance of {@link TvOverlayManager}. 1166 */ getOverlayManager()1167 public TvOverlayManager getOverlayManager() { 1168 return mOverlayManager; 1169 } 1170 1171 /** 1172 * Returns the {@link ConflictChecker}. 1173 */ 1174 @Nullable getDvrConflictChecker()1175 public ConflictChecker getDvrConflictChecker() { 1176 return mDvrConflictChecker; 1177 } 1178 getCurrentChannel()1179 public Channel getCurrentChannel() { 1180 return mChannelTuner.getCurrentChannel(); 1181 } 1182 getCurrentChannelId()1183 public long getCurrentChannelId() { 1184 return mChannelTuner.getCurrentChannelId(); 1185 } 1186 1187 /** 1188 * Returns true if the current connected TV supports AC3 passthough. 1189 */ isAc3PassthroughSupported()1190 public boolean isAc3PassthroughSupported() { 1191 return mAc3PassthroughSupported; 1192 } 1193 1194 /** 1195 * Returns the current program which the user is watching right now.<p> 1196 * 1197 * It might be a live program. If the time shifting is available, it can be a past program, too. 1198 */ getCurrentProgram()1199 public Program getCurrentProgram() { 1200 if (!isChannelChangeKeyDownReceived() && mTimeShiftManager.isAvailable()) { 1201 // We shouldn't get current program from TimeShiftManager during channel tunning 1202 return mTimeShiftManager.getCurrentProgram(); 1203 } 1204 return mProgramDataManager.getCurrentProgram(getCurrentChannelId()); 1205 } 1206 1207 /** 1208 * Returns the current playing time in milliseconds.<p> 1209 * 1210 * If the time shifting is available, the time is the playing position of the program, 1211 * otherwise, the system current time. 1212 */ getCurrentPlayingPosition()1213 public long getCurrentPlayingPosition() { 1214 if (mTimeShiftManager.isAvailable()) { 1215 return mTimeShiftManager.getCurrentPositionMs(); 1216 } 1217 return System.currentTimeMillis(); 1218 } 1219 getBrowsableChannel()1220 private Channel getBrowsableChannel() { 1221 // TODO: mChannelMap could be dirty for a while when the browsablity of channels 1222 // are changed. In that case, we shouldn't use the value from mChannelMap. 1223 Channel curChannel = mChannelTuner.getCurrentChannel(); 1224 if (curChannel != null && curChannel.isBrowsable()) { 1225 return curChannel; 1226 } else { 1227 return mChannelTuner.getAdjacentBrowsableChannel(true); 1228 } 1229 } 1230 1231 /** 1232 * Call {@link Activity#startActivity} in a safe way. 1233 * 1234 * @see LauncherActivity 1235 */ startActivitySafe(Intent intent)1236 public void startActivitySafe(Intent intent) { 1237 LauncherActivity.startActivitySafe(this, intent); 1238 } 1239 1240 /** 1241 * Call {@link Activity#startActivityForResult} in a safe way. 1242 * 1243 * @see LauncherActivity 1244 */ startActivityForResultSafe(Intent intent, int requestCode)1245 private void startActivityForResultSafe(Intent intent, int requestCode) { 1246 LauncherActivity.startActivityForResultSafe(this, intent, requestCode); 1247 } 1248 1249 /** 1250 * Show settings fragment. 1251 */ showSettingsFragment()1252 public void showSettingsFragment() { 1253 if (!mChannelTuner.areAllChannelsLoaded()) { 1254 // Show ChannelSourcesFragment only if all the channels are loaded. 1255 return; 1256 } 1257 Channel currentChannel = mChannelTuner.getCurrentChannel(); 1258 long channelId = currentChannel == null ? Channel.INVALID_ID : currentChannel.getId(); 1259 mOverlayManager.getSideFragmentManager().show(new SettingsFragment(channelId)); 1260 } 1261 showMerchantCollection()1262 public void showMerchantCollection() { 1263 startActivitySafe(OnboardingUtils.ONLINE_STORE_INTENT); 1264 } 1265 1266 /** 1267 * It is called when shrunken TvView is desired, such as EditChannelFragment and 1268 * ChannelsLockedFragment. 1269 */ startShrunkenTvView(boolean showLockedChannelsTemporarily, boolean willMainViewBeTunerInput)1270 public void startShrunkenTvView(boolean showLockedChannelsTemporarily, 1271 boolean willMainViewBeTunerInput) { 1272 mChannelBeforeShrunkenTvView = mTvView.getCurrentChannel(); 1273 mWasChannelUnblockedBeforeShrunkenByUser = mIsCurrentChannelUnblockedByUser; 1274 mAllowedRatingBeforeShrunken = mLastAllowedRatingForCurrentChannel; 1275 1276 if (willMainViewBeTunerInput && mChannelTuner.isCurrentChannelPassthrough() 1277 && mPipEnabled) { 1278 mPipChannelBeforeShrunkenTvView = mPipChannel; 1279 enablePipView(false, false); 1280 } else { 1281 mPipChannelBeforeShrunkenTvView = null; 1282 } 1283 mTvViewUiManager.startShrunkenTvView(); 1284 1285 if (showLockedChannelsTemporarily) { 1286 mShowLockedChannelsTemporarily = true; 1287 checkChannelLockNeeded(mTvView); 1288 } 1289 1290 mTvView.setBlockScreenType(getDesiredBlockScreenType()); 1291 } 1292 1293 /** 1294 * It is called when shrunken TvView is no longer desired, such as EditChannelFragment and 1295 * ChannelsLockedFragment. 1296 */ endShrunkenTvView()1297 public void endShrunkenTvView() { 1298 mTvViewUiManager.endShrunkenTvView(); 1299 mIsCompletingShrunkenTvView = true; 1300 1301 Channel returnChannel = mChannelBeforeShrunkenTvView; 1302 if (returnChannel == null 1303 || (!returnChannel.isPassthrough() && !returnChannel.isBrowsable())) { 1304 // Try to tune to the next best channel instead. 1305 returnChannel = getBrowsableChannel(); 1306 } 1307 mShowLockedChannelsTemporarily = false; 1308 1309 // The current channel is mTvView.getCurrentChannel() and need to tune to the returnChannel. 1310 if (!Objects.equals(mTvView.getCurrentChannel(), returnChannel)) { 1311 final Channel channel = returnChannel; 1312 Runnable tuneAction = new Runnable() { 1313 @Override 1314 public void run() { 1315 tuneToChannel(channel); 1316 if (mChannelBeforeShrunkenTvView == null 1317 || !mChannelBeforeShrunkenTvView.equals(channel)) { 1318 Utils.setLastWatchedChannel(MainActivity.this, channel); 1319 } 1320 mIsCompletingShrunkenTvView = false; 1321 mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser; 1322 mTvView.setBlockScreenType(getDesiredBlockScreenType()); 1323 if (mPipChannelBeforeShrunkenTvView != null) { 1324 enablePipView(true, false); 1325 mPipChannelBeforeShrunkenTvView = null; 1326 } 1327 } 1328 }; 1329 mTvViewUiManager.fadeOutTvView(tuneAction); 1330 // Will automatically fade-in when video becomes available. 1331 } else { 1332 checkChannelLockNeeded(mTvView); 1333 mIsCompletingShrunkenTvView = false; 1334 mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser; 1335 mTvView.setBlockScreenType(getDesiredBlockScreenType()); 1336 if (mPipChannelBeforeShrunkenTvView != null) { 1337 enablePipView(true, false); 1338 mPipChannelBeforeShrunkenTvView = null; 1339 } 1340 } 1341 } 1342 isUnderShrunkenTvView()1343 private boolean isUnderShrunkenTvView() { 1344 return mTvViewUiManager.isUnderShrunkenTvView() || mIsCompletingShrunkenTvView; 1345 } 1346 1347 @Override onActivityResult(int requestCode, int resultCode, Intent data)1348 public void onActivityResult(int requestCode, int resultCode, Intent data) { 1349 switch (requestCode) { 1350 case REQUEST_CODE_START_SETUP_ACTIVITY: 1351 if (resultCode == RESULT_OK) { 1352 int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup); 1353 String text; 1354 if (count > 0) { 1355 text = getResources().getQuantityString(R.plurals.msg_channel_added, 1356 count, count); 1357 } else { 1358 text = getString(R.string.msg_no_channel_added); 1359 } 1360 Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show(); 1361 mInputIdUnderSetup = null; 1362 if (mChannelTuner.getCurrentChannel() == null) { 1363 mChannelTuner.moveToAdjacentBrowsableChannel(true); 1364 } 1365 if (mTunePending) { 1366 tune(); 1367 } 1368 } else { 1369 mInputIdUnderSetup = null; 1370 } 1371 if (!mIsSetupActivityCalledByPopup) { 1372 mOverlayManager.getSideFragmentManager().showSidePanel(false); 1373 } 1374 break; 1375 case REQUEST_CODE_START_SYSTEM_CAPTIONING_SETTINGS: 1376 mOverlayManager.getSideFragmentManager().showSidePanel(false); 1377 break; 1378 } 1379 if (data != null) { 1380 String errorMessage = data.getStringExtra(LauncherActivity.ERROR_MESSAGE); 1381 if (!TextUtils.isEmpty(errorMessage)) { 1382 Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_SHORT).show(); 1383 } 1384 } 1385 } 1386 1387 @Override dispatchKeyEvent(KeyEvent event)1388 public boolean dispatchKeyEvent(KeyEvent event) { 1389 if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "dispatchKeyEvent(" + event + ")"); 1390 // If an activity is closed on a back key down event, back key down events with none zero 1391 // repeat count or a back key up event can be happened without the first back key down 1392 // event which should be ignored in this activity. 1393 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1394 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 1395 mBackKeyPressed = true; 1396 } 1397 if (!mBackKeyPressed) { 1398 return true; 1399 } 1400 if (event.getAction() == KeyEvent.ACTION_UP) { 1401 mBackKeyPressed = false; 1402 } 1403 } 1404 1405 // When side panel is closing, it has the focus. 1406 // Keep the focus, but just don't deliver the key events. 1407 if ((mContentView.hasFocusable() && !mOverlayManager.getSideFragmentManager().isHiding()) 1408 || mOverlayManager.getSideFragmentManager().isActive()) { 1409 return super.dispatchKeyEvent(event); 1410 } 1411 if (BLACKLIST_KEYCODE_TO_TIS.contains(event.getKeyCode()) 1412 || KeyEvent.isGamepadButton(event.getKeyCode())) { 1413 // If the event is in blacklisted or gamepad key, do not pass it to session. 1414 // Gamepad keys are blacklisted to support TV UIs and here's the detail. 1415 // If there's a TIS granted RECEIVE_INPUT_EVENT, TIF sends key events to TIS 1416 // and return immediately saying that the event is handled. 1417 // In this case, fallback key will be injected but with FLAG_CANCELED 1418 // while gamepads support DPAD_CENTER and BACK by fallback. 1419 // Since we don't expect that TIS want to handle gamepad buttons now, 1420 // blacklist gamepad buttons and wait for next fallback keys. 1421 // TODO) Need to consider other fallback keys (e.g. ESCAPE) 1422 return super.dispatchKeyEvent(event); 1423 } 1424 return dispatchKeyEventToSession(event) || super.dispatchKeyEvent(event); 1425 } 1426 1427 @Override onAudioFocusChange(int focusChange)1428 public void onAudioFocusChange(int focusChange) { 1429 mAudioFocusStatus = focusChange; 1430 setVolumeByAudioFocusStatus(); 1431 } 1432 1433 /** 1434 * Notifies the key input focus is changed to the TV view. 1435 */ updateKeyInputFocus()1436 public void updateKeyInputFocus() { 1437 mHandler.post(new Runnable() { 1438 @Override 1439 public void run() { 1440 mTvView.setBlockScreenType(getDesiredBlockScreenType()); 1441 } 1442 }); 1443 } 1444 1445 // It should be called before onResume. handleIntent(Intent intent)1446 private boolean handleIntent(Intent intent) { 1447 // Reset the closed caption settings when the activity is 1)created or 2) restarted. 1448 // And do not reset while TvView is playing. 1449 if (!mTvView.isPlaying()) { 1450 mCaptionSettings = new CaptionSettings(this); 1451 } 1452 1453 // Handle the passed key press, if any. Note that only the key codes that are currently 1454 // handled in the TV app will be handled via Intent. 1455 // TODO: Consider defining a separate intent filter as passing data of mime type 1456 // vnd.android.cursor.item/channel isn't really necessary here. 1457 int keyCode = intent.getIntExtra(Utils.EXTRA_KEY_KEYCODE, KeyEvent.KEYCODE_UNKNOWN); 1458 if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { 1459 if (DEBUG) Log.d(TAG, "Got an intent with keycode: " + keyCode); 1460 KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, keyCode); 1461 onKeyUp(keyCode, event); 1462 return true; 1463 } 1464 mShouldTuneToTunerChannel = intent.getBooleanExtra(Utils.EXTRA_KEY_FROM_LAUNCHER, false); 1465 mInitChannelUri = null; 1466 1467 String extraAction = intent.getStringExtra(Utils.EXTRA_KEY_ACTION); 1468 if (!TextUtils.isEmpty(extraAction)) { 1469 if (DEBUG) Log.d(TAG, "Got an extra action: " + extraAction); 1470 if (Utils.EXTRA_ACTION_SHOW_TV_INPUT.equals(extraAction)) { 1471 String lastWatchedChannelUri = Utils.getLastWatchedChannelUri(this); 1472 if (lastWatchedChannelUri != null) { 1473 mInitChannelUri = Uri.parse(lastWatchedChannelUri); 1474 } 1475 mShowSelectInputView = true; 1476 } 1477 } 1478 1479 // TODO: remove the checkState once N API is finalized. 1480 SoftPreconditions.checkState(TvInputManager.ACTION_SETUP_INPUTS.equals( 1481 "android.media.tv.action.SETUP_INPUTS")); 1482 if (TvInputManager.ACTION_SETUP_INPUTS.equals(intent.getAction())) { 1483 runAfterAttachedToWindow(new Runnable() { 1484 @Override 1485 public void run() { 1486 mOverlayManager.showSetupFragment(); 1487 } 1488 }); 1489 } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { 1490 Uri uri = intent.getData(); 1491 try { 1492 mSource = uri.getQueryParameter(Utils.PARAM_SOURCE); 1493 } catch (UnsupportedOperationException e) { 1494 // ignore this exception. 1495 } 1496 // When the URI points to the programs (directory, not an individual item), go to the 1497 // program guide. The intention here is to respond to 1498 // "content://android.media.tv/program", not "content://android.media.tv/program/XXX". 1499 // Later, we might want to add handling of individual programs too. 1500 if (Utils.isProgramsUri(uri)) { 1501 // The given data is a programs URI. Open the Program Guide. 1502 mShowProgramGuide = true; 1503 return true; 1504 } 1505 // In case the channel is given explicitly, use it. 1506 mInitChannelUri = uri; 1507 if (DEBUG) Log.d(TAG, "ACTION_VIEW with " + mInitChannelUri); 1508 if (Channels.CONTENT_URI.equals(mInitChannelUri)) { 1509 // Tune to default channel. 1510 mInitChannelUri = null; 1511 mShouldTuneToTunerChannel = true; 1512 return true; 1513 } 1514 if ((!Utils.isChannelUriForOneChannel(mInitChannelUri) 1515 && !Utils.isChannelUriForInput(mInitChannelUri))) { 1516 Log.w(TAG, "Malformed channel uri " + mInitChannelUri 1517 + " tuning to default instead"); 1518 mInitChannelUri = null; 1519 return true; 1520 } 1521 mTuneParams = intent.getExtras(); 1522 if (mTuneParams == null) { 1523 mTuneParams = new Bundle(); 1524 } 1525 if (Utils.isChannelUriForTunerInput(mInitChannelUri)) { 1526 long channelId = ContentUris.parseId(mInitChannelUri); 1527 mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId); 1528 } else if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) { 1529 // If mInitChannelUri is for a passthrough TV input. 1530 String inputId = mInitChannelUri.getPathSegments().get(1); 1531 TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(inputId); 1532 if (input == null) { 1533 mInitChannelUri = null; 1534 Toast.makeText(this, R.string.msg_no_specific_input, Toast.LENGTH_SHORT).show(); 1535 return false; 1536 } else if (!input.isPassthroughInput()) { 1537 mInitChannelUri = null; 1538 Toast.makeText(this, R.string.msg_not_passthrough_input, Toast.LENGTH_SHORT) 1539 .show(); 1540 return false; 1541 } 1542 } else if (mInitChannelUri != null) { 1543 // Handle the URI built by TvContract.buildChannelsUriForInput(). 1544 // TODO: Change hard-coded "input" to TvContract.PARAM_INPUT. 1545 String inputId = mInitChannelUri.getQueryParameter("input"); 1546 long channelId = Utils.getLastWatchedChannelIdForInput(this, inputId); 1547 if (channelId == Channel.INVALID_ID) { 1548 String[] projection = { Channels._ID }; 1549 try (Cursor cursor = getContentResolver().query(uri, projection, 1550 null, null, null)) { 1551 if (cursor != null && cursor.moveToNext()) { 1552 channelId = cursor.getLong(0); 1553 } 1554 } 1555 } 1556 if (channelId == Channel.INVALID_ID) { 1557 // Couldn't find any channel probably because the input hasn't been set up. 1558 // Try to set it up. 1559 mInitChannelUri = null; 1560 mInputToSetUp = mTvInputManagerHelper.getTvInputInfo(inputId); 1561 } else { 1562 mInitChannelUri = TvContract.buildChannelUri(channelId); 1563 mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId); 1564 } 1565 } 1566 } 1567 return true; 1568 } 1569 setVolumeByAudioFocusStatus()1570 private void setVolumeByAudioFocusStatus() { 1571 if (mPipSound == TvSettings.PIP_SOUND_MAIN) { 1572 setVolumeByAudioFocusStatus(mTvView); 1573 } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW 1574 setVolumeByAudioFocusStatus(mPipView); 1575 } 1576 } 1577 setVolumeByAudioFocusStatus(TunableTvView tvView)1578 private void setVolumeByAudioFocusStatus(TunableTvView tvView) { 1579 SoftPreconditions.checkState(tvView == mTvView || tvView == mPipView); 1580 if (tvView.isPlaying()) { 1581 switch (mAudioFocusStatus) { 1582 case AudioManager.AUDIOFOCUS_GAIN: 1583 tvView.setStreamVolume(AUDIO_MAX_VOLUME); 1584 break; 1585 case AudioManager.AUDIOFOCUS_LOSS: 1586 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 1587 tvView.setStreamVolume(AUDIO_MIN_VOLUME); 1588 break; 1589 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 1590 tvView.setStreamVolume(AUDIO_DUCKING_VOLUME); 1591 break; 1592 } 1593 } 1594 if (tvView == mTvView) { 1595 if (mPipView != null && mPipView.isPlaying()) { 1596 mPipView.setStreamVolume(AUDIO_MIN_VOLUME); 1597 } 1598 } else { // tvView == mPipView 1599 if (mTvView != null && mTvView.isPlaying()) { 1600 mTvView.setStreamVolume(AUDIO_MIN_VOLUME); 1601 } 1602 } 1603 } 1604 stopTv()1605 private void stopTv() { 1606 stopTv(null, false); 1607 } 1608 stopTv(String logForCaller, boolean keepVisibleBehind)1609 private void stopTv(String logForCaller, boolean keepVisibleBehind) { 1610 if (logForCaller != null) { 1611 Log.i(TAG, "stopTv is called at " + logForCaller + "."); 1612 } else { 1613 if (DEBUG) Log.d(TAG, "stopTv()"); 1614 } 1615 if (mTvView.isPlaying()) { 1616 mTvView.stop(); 1617 if (!keepVisibleBehind) { 1618 requestVisibleBehind(false); 1619 } 1620 mAudioManager.abandonAudioFocus(this); 1621 if (mMediaSession.isActive()) { 1622 mMediaSession.setActive(false); 1623 } 1624 } 1625 TvApplication.getSingletons(this).getMainActivityWrapper() 1626 .notifyCurrentChannelChange(this, null); 1627 mChannelTuner.resetCurrentChannel(); 1628 mTunePending = false; 1629 } 1630 isPlaying()1631 private boolean isPlaying() { 1632 return mTvView.isPlaying() && mTvView.getCurrentChannel() != null; 1633 } 1634 startPip(final boolean fromUserInteraction)1635 private void startPip(final boolean fromUserInteraction) { 1636 if (mPipChannel == null) { 1637 Log.w(TAG, "PIP channel id is an invalid id."); 1638 return; 1639 } 1640 if (DEBUG) Log.d(TAG, "startPip() " + mPipChannel); 1641 mPipView.start(mTvInputManagerHelper); 1642 boolean success = mPipView.tuneTo(mPipChannel, null, new OnTuneListener() { 1643 @Override 1644 public void onUnexpectedStop(Channel channel) { 1645 Log.w(TAG, "The PIP is Unexpectedly stopped"); 1646 enablePipView(false, false); 1647 } 1648 1649 @Override 1650 public void onTuneFailed(Channel channel) { 1651 Log.w(TAG, "Fail to start the PIP during channel tuning"); 1652 if (fromUserInteraction) { 1653 Toast.makeText(MainActivity.this, R.string.msg_no_pip_support, 1654 Toast.LENGTH_SHORT).show(); 1655 enablePipView(false, false); 1656 } 1657 } 1658 1659 @Override 1660 public void onStreamInfoChanged(StreamInfo info) { 1661 mTvViewUiManager.updatePipView(); 1662 mHandler.removeCallbacks(mRestoreMainViewRunnable); 1663 restoreMainTvView(); 1664 } 1665 1666 @Override 1667 public void onChannelRetuned(Uri channel) { 1668 if (channel == null) { 1669 return; 1670 } 1671 Channel currentChannel = 1672 mChannelDataManager.getChannel(ContentUris.parseId(channel)); 1673 if (currentChannel == null) { 1674 Log.e(TAG, "onChannelRetuned is called from PIP input but can't find a channel" 1675 + " with the URI " + channel); 1676 return; 1677 } 1678 if (isChannelChangeKeyDownReceived()) { 1679 // Ignore this message if the user is changing the channel. 1680 return; 1681 } 1682 mPipChannel = currentChannel; 1683 mPipView.setCurrentChannel(mPipChannel); 1684 } 1685 1686 @Override 1687 public void onContentBlocked() { 1688 updateMediaSession(); 1689 } 1690 1691 @Override 1692 public void onContentAllowed() { 1693 updateMediaSession(); 1694 } 1695 }); 1696 if (!success) { 1697 Log.w(TAG, "Fail to start the PIP"); 1698 return; 1699 } 1700 if (fromUserInteraction) { 1701 checkChannelLockNeeded(mPipView); 1702 } 1703 // Explicitly make the PIP view main to make the selected input an HDMI-CEC active source. 1704 mPipView.setMain(); 1705 scheduleRestoreMainTvView(); 1706 mTvViewUiManager.onPipStart(); 1707 setVolumeByAudioFocusStatus(); 1708 } 1709 scheduleRestoreMainTvView()1710 private void scheduleRestoreMainTvView() { 1711 mHandler.removeCallbacks(mRestoreMainViewRunnable); 1712 mHandler.postDelayed(mRestoreMainViewRunnable, TVVIEW_SET_MAIN_TIMEOUT_MS); 1713 } 1714 stopPip()1715 private void stopPip() { 1716 if (DEBUG) Log.d(TAG, "stopPip"); 1717 if (mPipView.isPlaying()) { 1718 mPipView.stop(); 1719 mPipSwap = false; 1720 mTvViewUiManager.onPipStop(); 1721 } 1722 } 1723 1724 /** 1725 * Says {@code text} when accessibility is turned on. 1726 */ sendAccessibilityText(String text)1727 private void sendAccessibilityText(String text) { 1728 if (mAccessibilityManager.isEnabled()) { 1729 AccessibilityEvent event = AccessibilityEvent.obtain(); 1730 event.setClassName(getClass().getName()); 1731 event.setPackageName(getPackageName()); 1732 event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT); 1733 event.getText().add(text); 1734 mAccessibilityManager.sendAccessibilityEvent(event); 1735 } 1736 } 1737 tune()1738 private void tune() { 1739 if (DEBUG) Log.d(TAG, "tune()"); 1740 mTuneDurationTimer.start(); 1741 1742 lazyInitializeIfNeeded(); 1743 1744 // Prerequisites to be able to tune. 1745 if (mInputIdUnderSetup != null) { 1746 mTunePending = true; 1747 return; 1748 } 1749 mTunePending = false; 1750 final Channel channel = mChannelTuner.getCurrentChannel(); 1751 if (!mChannelTuner.isCurrentChannelPassthrough()) { 1752 if (mTvInputManagerHelper.getTunerTvInputSize() == 0) { 1753 Toast.makeText(this, R.string.msg_no_input, Toast.LENGTH_SHORT).show(); 1754 finish(); 1755 return; 1756 } 1757 SetupUtils setupUtils = SetupUtils.getInstance(this); 1758 if (setupUtils.isFirstTune()) { 1759 if (!mChannelTuner.areAllChannelsLoaded()) { 1760 // tune() will be called, once all channels are loaded. 1761 stopTv("tune()", false); 1762 return; 1763 } 1764 if (mChannelDataManager.getChannelCount() > 0) { 1765 mOverlayManager.showIntroDialog(); 1766 } 1767 } 1768 if (!TvCommonUtils.isRunningInTest() && mShowNewSourcesFragment 1769 && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) { 1770 // Show new channel sources fragment. 1771 runAfterAttachedToWindow(new Runnable() { 1772 @Override 1773 public void run() { 1774 mOverlayManager.runAfterOverlaysAreClosed(new Runnable() { 1775 @Override 1776 public void run() { 1777 mOverlayManager.showNewSourcesFragment(); 1778 } 1779 }); 1780 } 1781 }); 1782 } 1783 mShowNewSourcesFragment = false; 1784 if (mChannelTuner.getBrowsableChannelCount() == 0 1785 && mChannelDataManager.getChannelCount() > 0 1786 && !mOverlayManager.getSideFragmentManager().isActive()) { 1787 if (!mChannelTuner.areAllChannelsLoaded()) { 1788 return; 1789 } 1790 if (mTvInputManagerHelper.getTunerTvInputSize() == 1) { 1791 mOverlayManager.getSideFragmentManager().show( 1792 new CustomizeChannelListFragment()); 1793 } else { 1794 showSettingsFragment(); 1795 } 1796 return; 1797 } 1798 // TODO: need to refactor the following code to put in startTv. 1799 if (channel == null) { 1800 // There is no channel to tune to. 1801 stopTv("tune()", false); 1802 if (!mChannelDataManager.isDbLoadFinished()) { 1803 // Wait until channel data is loaded in order to know the number of channels. 1804 // tune() will be retried, once the channel data is loaded. 1805 return; 1806 } 1807 if (mOverlayManager.getSideFragmentManager().isActive()) { 1808 return; 1809 } 1810 mOverlayManager.showSetupFragment(); 1811 return; 1812 } 1813 setupUtils.onTuned(); 1814 if (mTuneParams != null) { 1815 Long initChannelId = mTuneParams.getLong(KEY_INIT_CHANNEL_ID); 1816 if (initChannelId == channel.getId()) { 1817 mTuneParams.remove(KEY_INIT_CHANNEL_ID); 1818 } else { 1819 mTuneParams = null; 1820 } 1821 } 1822 } 1823 1824 mIsCurrentChannelUnblockedByUser = false; 1825 if (!isUnderShrunkenTvView()) { 1826 mLastAllowedRatingForCurrentChannel = null; 1827 } 1828 mHandler.removeMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE); 1829 // For every tune, we need to inform the tuned channel or input to a user, 1830 // if Talkback is turned on. 1831 sendAccessibilityText(!mChannelTuner.isCurrentChannelPassthrough() ? 1832 Utils.loadLabel(this, mTvInputManagerHelper.getTvInputInfo(channel.getInputId())) 1833 : channel.getDisplayText()); 1834 1835 boolean success = mTvView.tuneTo(channel, mTuneParams, mOnTuneListener); 1836 mOnTuneListener.onTune(channel, isUnderShrunkenTvView()); 1837 1838 mTuneParams = null; 1839 if (!success) { 1840 Toast.makeText(this, R.string.msg_tune_failed, Toast.LENGTH_SHORT).show(); 1841 return; 1842 } 1843 1844 // Explicitly make the TV view main to make the selected input an HDMI-CEC active source. 1845 mTvView.setMain(); 1846 scheduleRestoreMainTvView(); 1847 if (!isUnderShrunkenTvView()) { 1848 if (!channel.isPassthrough()) { 1849 addToRecentChannels(channel.getId()); 1850 } 1851 Utils.setLastWatchedChannel(this, channel); 1852 TvApplication.getSingletons(this).getMainActivityWrapper() 1853 .notifyCurrentChannelChange(this, channel); 1854 } 1855 checkChannelLockNeeded(mTvView); 1856 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE); 1857 if (mActivityResumed) { 1858 // requestVisibleBehind should be called after onResume() is called. But, when 1859 // launcher is over the TV app and the screen is turned off and on, tune() can 1860 // be called during the pause state by mBroadcastReceiver (Intent.ACTION_SCREEN_ON). 1861 requestVisibleBehind(true); 1862 } 1863 updateMediaSession(); 1864 } 1865 1866 // Runs the runnable after the activity is attached to window to show the fragment transition 1867 // animation. 1868 // The runnable runs asynchronously to show the animation a little better even when system is 1869 // busy at the moment it is called. 1870 // If the activity is paused shortly, runnable may not be called because all the fragments 1871 // should be closed when the activity is paused. runAfterAttachedToWindow(final Runnable runnable)1872 private void runAfterAttachedToWindow(final Runnable runnable) { 1873 final Runnable runOnlyIfActivityIsResumed = new Runnable() { 1874 @Override 1875 public void run() { 1876 if (mActivityResumed) { 1877 runnable.run(); 1878 } 1879 } 1880 }; 1881 if (mContentView.isAttachedToWindow()) { 1882 mHandler.post(runOnlyIfActivityIsResumed); 1883 } else { 1884 mContentView.getViewTreeObserver().addOnWindowAttachListener( 1885 new ViewTreeObserver.OnWindowAttachListener() { 1886 @Override 1887 public void onWindowAttached() { 1888 mContentView.getViewTreeObserver().removeOnWindowAttachListener(this); 1889 mHandler.post(runOnlyIfActivityIsResumed); 1890 } 1891 1892 @Override 1893 public void onWindowDetached() { } 1894 }); 1895 } 1896 } 1897 updateMediaSession()1898 private void updateMediaSession() { 1899 if (getCurrentChannel() == null) { 1900 mMediaSession.setActive(false); 1901 return; 1902 } 1903 1904 // If the channel is blocked, display a lock and a short text on the Now Playing Card 1905 if (mTvView.isScreenBlocked() || mTvView.getBlockedContentRating() != null) { 1906 setMediaSessionPlaybackState(false); 1907 1908 Bitmap art = BitmapFactory.decodeResource( 1909 getResources(), R.drawable.ic_message_lock_preview); 1910 updateMediaMetadata( 1911 getResources().getString(R.string.channel_banner_locked_channel_title), art); 1912 mMediaSession.setActive(true); 1913 return; 1914 } 1915 1916 final Program currentProgram = getCurrentProgram(); 1917 String cardTitleText = null; 1918 String posterArtUri = null; 1919 if (currentProgram != null) { 1920 cardTitleText = currentProgram.getTitle(); 1921 posterArtUri = currentProgram.getPosterArtUri(); 1922 } 1923 if (TextUtils.isEmpty(cardTitleText)) { 1924 cardTitleText = getCurrentChannelName(); 1925 } 1926 updateMediaMetadata(cardTitleText, null); 1927 setMediaSessionPlaybackState(true); 1928 1929 if (posterArtUri == null) { 1930 posterArtUri = TvContract.buildChannelLogoUri(getCurrentChannelId()).toString(); 1931 } 1932 updatePosterArt(getCurrentChannel(), currentProgram, cardTitleText, null, posterArtUri); 1933 mMediaSession.setActive(true); 1934 } 1935 updatePosterArt(Channel currentChannel, Program currentProgram, String cardTitleText, @Nullable Bitmap posterArt, @Nullable String posterArtUri)1936 private void updatePosterArt(Channel currentChannel, Program currentProgram, 1937 String cardTitleText, @Nullable Bitmap posterArt, @Nullable String posterArtUri) { 1938 if (posterArt != null) { 1939 updateMediaMetadata(cardTitleText, posterArt); 1940 } else if (posterArtUri != null) { 1941 ImageLoader.loadBitmap(this, posterArtUri, mNowPlayingCardWidth, mNowPlayingCardHeight, 1942 new ProgramPosterArtCallback(this, currentChannel, 1943 currentProgram, cardTitleText)); 1944 } else { 1945 updateMediaMetadata(cardTitleText, R.drawable.default_now_card); 1946 } 1947 } 1948 1949 private static class ProgramPosterArtCallback extends 1950 ImageLoader.ImageLoaderCallback<MainActivity> { 1951 private final Channel mChannel; 1952 private final Program mProgram; 1953 private final String mCardTitleText; 1954 ProgramPosterArtCallback(MainActivity mainActivity, Channel channel, Program program, String cardTitleText)1955 public ProgramPosterArtCallback(MainActivity mainActivity, Channel channel, Program program, 1956 String cardTitleText) { 1957 super(mainActivity); 1958 mChannel = channel; 1959 mProgram = program; 1960 mCardTitleText = cardTitleText; 1961 } 1962 1963 @Override onBitmapLoaded(MainActivity mainActivity, @Nullable Bitmap posterArt)1964 public void onBitmapLoaded(MainActivity mainActivity, @Nullable Bitmap posterArt) { 1965 if (mainActivity.isNowPlayingProgram(mChannel, mProgram)) { 1966 mainActivity.updatePosterArt(mChannel, mProgram, mCardTitleText, posterArt, null); 1967 } 1968 } 1969 } 1970 isNowPlayingProgram(Channel channel, Program program)1971 private boolean isNowPlayingProgram(Channel channel, Program program) { 1972 return program == null ? (channel != null && getCurrentProgram() == null 1973 && channel.equals(getCurrentChannel())) : program.equals(getCurrentProgram()); 1974 } 1975 updateMediaMetadata(final String title, final Bitmap posterArt)1976 private void updateMediaMetadata(final String title, final Bitmap posterArt) { 1977 new AsyncTask<Void, Void, Void> () { 1978 @Override 1979 protected Void doInBackground(Void... arg0) { 1980 MediaMetadata.Builder builder = new MediaMetadata.Builder(); 1981 builder.putString(MediaMetadata.METADATA_KEY_TITLE, title); 1982 if (posterArt != null) { 1983 builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt); 1984 } 1985 mMediaSession.setMetadata(builder.build()); 1986 return null; 1987 } 1988 }.execute(); 1989 } 1990 updateMediaMetadata(final String title, final int imageResId)1991 private void updateMediaMetadata(final String title, final int imageResId) { 1992 new AsyncTask<Void, Void, Void> () { 1993 @Override 1994 protected Void doInBackground(Void... arg0) { 1995 MediaMetadata.Builder builder = new MediaMetadata.Builder(); 1996 builder.putString(MediaMetadata.METADATA_KEY_TITLE, title); 1997 Bitmap posterArt = BitmapFactory.decodeResource(getResources(), imageResId); 1998 if (posterArt != null) { 1999 builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt); 2000 } 2001 mMediaSession.setMetadata(builder.build()); 2002 return null; 2003 } 2004 }.execute(); 2005 } 2006 getCurrentChannelName()2007 private String getCurrentChannelName() { 2008 Channel channel = getCurrentChannel(); 2009 if (channel == null) { 2010 return ""; 2011 } 2012 if (channel.isPassthrough()) { 2013 TvInputInfo input = getTvInputManagerHelper().getTvInputInfo(channel.getInputId()); 2014 return Utils.loadLabel(this, input); 2015 } else { 2016 return channel.getDisplayName(); 2017 } 2018 } 2019 setMediaSessionPlaybackState(boolean isPlaying)2020 private void setMediaSessionPlaybackState(boolean isPlaying) { 2021 PlaybackState.Builder builder = new PlaybackState.Builder(); 2022 builder.setState(isPlaying ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_STOPPED, 2023 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 2024 isPlaying ? MEDIA_SESSION_PLAYING_SPEED : MEDIA_SESSION_STOPPED_SPEED); 2025 mMediaSession.setPlaybackState(builder.build()); 2026 } 2027 addToRecentChannels(long channelId)2028 private void addToRecentChannels(long channelId) { 2029 if (!mRecentChannels.remove(channelId)) { 2030 if (mRecentChannels.size() >= MAX_RECENT_CHANNELS) { 2031 mRecentChannels.removeLast(); 2032 } 2033 } 2034 mRecentChannels.addFirst(channelId); 2035 mOverlayManager.getMenu().onRecentChannelsChanged(); 2036 } 2037 2038 /** 2039 * Returns the recently tuned channels. 2040 */ getRecentChannels()2041 public ArrayDeque<Long> getRecentChannels() { 2042 return mRecentChannels; 2043 } 2044 checkChannelLockNeeded(TunableTvView tvView)2045 private void checkChannelLockNeeded(TunableTvView tvView) { 2046 Channel channel = tvView.getCurrentChannel(); 2047 if (tvView.isPlaying() && channel != null) { 2048 if (getParentalControlSettings().isParentalControlsEnabled() 2049 && channel.isLocked() 2050 && !mShowLockedChannelsTemporarily 2051 && !(isUnderShrunkenTvView() 2052 && channel.equals(mChannelBeforeShrunkenTvView) 2053 && mWasChannelUnblockedBeforeShrunkenByUser)) { 2054 if (DEBUG) Log.d(TAG, "Channel " + channel.getId() + " is locked"); 2055 blockScreen(tvView); 2056 } else { 2057 unblockScreen(tvView); 2058 } 2059 } 2060 } 2061 blockScreen(TunableTvView tvView)2062 private void blockScreen(TunableTvView tvView) { 2063 tvView.blockScreen(); 2064 if (tvView == mTvView) { 2065 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); 2066 updateMediaSession(); 2067 } 2068 } 2069 unblockScreen(TunableTvView tvView)2070 private void unblockScreen(TunableTvView tvView) { 2071 tvView.unblockScreen(); 2072 if (tvView == mTvView) { 2073 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); 2074 updateMediaSession(); 2075 } 2076 } 2077 2078 /** 2079 * Shows the channel banner if it was hidden from the side fragment. 2080 * 2081 * <p>When the side fragment is visible, showing the channel banner should be put off until the 2082 * side fragment is closed even though the channel changes. 2083 */ showChannelBannerIfHiddenBySideFragment()2084 public void showChannelBannerIfHiddenBySideFragment() { 2085 if (mChannelBannerHiddenBySideFragment) { 2086 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); 2087 } 2088 } 2089 updateChannelBannerAndShowIfNeeded(@hannelBannerUpdateReason int reason)2090 private void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) { 2091 if(DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")"); 2092 if (!mChannelTuner.isCurrentChannelPassthrough()) { 2093 int lockType = ChannelBannerView.LOCK_NONE; 2094 if (mTvView.isScreenBlocked()) { 2095 lockType = ChannelBannerView.LOCK_CHANNEL_INFO; 2096 } else if (mTvView.getBlockedContentRating() != null 2097 || (getParentalControlSettings().isParentalControlsEnabled() 2098 && !mTvView.isVideoAvailable())) { 2099 // If the parental control is enabled, do not show the program detail until the 2100 // video becomes available. 2101 lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; 2102 } 2103 if (lockType == ChannelBannerView.LOCK_NONE) { 2104 if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) { 2105 // Do not show detailed program information while fast-tuning. 2106 lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; 2107 } else if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE 2108 && getParentalControlSettings().isParentalControlsEnabled()) { 2109 // If parental control is turned on, 2110 // assumes that program is locked by default and waits for onContentAllowed. 2111 lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; 2112 } 2113 } 2114 // If lock type is not changed, we don't need to update channel banner by parental 2115 // control. 2116 if (!mChannelBannerView.setLockType(lockType) 2117 && reason == UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK) { 2118 return; 2119 } 2120 2121 mChannelBannerView.updateViews(mTvView); 2122 } 2123 boolean needToShowBanner = (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW 2124 || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE 2125 || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); 2126 boolean noOverlayUiWhenResume = 2127 mInputToSetUp == null && !mShowProgramGuide && !mShowSelectInputView; 2128 if (needToShowBanner && noOverlayUiWhenResume 2129 && mOverlayManager.getCurrentDialog() == null 2130 && !mOverlayManager.isSetupFragmentActive() 2131 && !mOverlayManager.isNewSourcesFragmentActive()) { 2132 if (mChannelTuner.getCurrentChannel() == null) { 2133 mChannelBannerHiddenBySideFragment = false; 2134 } else if (mOverlayManager.getSideFragmentManager().isActive()) { 2135 mChannelBannerHiddenBySideFragment = true; 2136 } else { 2137 mChannelBannerHiddenBySideFragment = false; 2138 mOverlayManager.showBanner(); 2139 } 2140 } 2141 updateAvailabilityToast(); 2142 } 2143 2144 /** 2145 * Hide the overlays when tuning to a channel from the menu (e.g. Channels). 2146 */ hideOverlaysForTune()2147 public void hideOverlaysForTune() { 2148 mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE); 2149 } 2150 needToKeepSetupScreenWhenHidingOverlay()2151 public boolean needToKeepSetupScreenWhenHidingOverlay() { 2152 return mInputIdUnderSetup != null && mIsSetupActivityCalledByPopup; 2153 } 2154 2155 // For now, this only takes care of 24fps. applyDisplayRefreshRate(float videoFrameRate)2156 private void applyDisplayRefreshRate(float videoFrameRate) { 2157 boolean is24Fps = Math.abs(videoFrameRate - FRAME_RATE_FOR_FILM) < FRAME_RATE_EPSILON; 2158 if (mIsFilmModeSet && !is24Fps) { 2159 setPreferredRefreshRate(mDefaultRefreshRate); 2160 mIsFilmModeSet = false; 2161 } else if (!mIsFilmModeSet && is24Fps) { 2162 DisplayManager displayManager = (DisplayManager) getSystemService( 2163 Context.DISPLAY_SERVICE); 2164 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 2165 2166 float[] refreshRates = display.getSupportedRefreshRates(); 2167 for (float refreshRate : refreshRates) { 2168 // Be conservative and set only when the display refresh rate supports 24fps. 2169 if (Math.abs(videoFrameRate - refreshRate) < REFRESH_RATE_EPSILON) { 2170 setPreferredRefreshRate(refreshRate); 2171 mIsFilmModeSet = true; 2172 return; 2173 } 2174 } 2175 } 2176 } 2177 2178 private void setPreferredRefreshRate(float refreshRate) { 2179 Window window = getWindow(); 2180 WindowManager.LayoutParams layoutParams = window.getAttributes(); 2181 layoutParams.preferredRefreshRate = refreshRate; 2182 window.setAttributes(layoutParams); 2183 } 2184 2185 private void applyMultiAudio() { 2186 List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO); 2187 if (tracks == null) { 2188 mTvOptionsManager.onMultiAudioChanged(null); 2189 return; 2190 } 2191 2192 String id = TvSettings.getMultiAudioId(this); 2193 String language = TvSettings.getMultiAudioLanguage(this); 2194 int channelCount = TvSettings.getMultiAudioChannelCount(this); 2195 TvTrackInfo bestTrack = TvTrackInfoUtils 2196 .getBestTrackInfo(tracks, id, language, channelCount); 2197 if (bestTrack != null) { 2198 String selectedTrack = getSelectedTrack(TvTrackInfo.TYPE_AUDIO); 2199 if (!bestTrack.getId().equals(selectedTrack)) { 2200 selectTrack(TvTrackInfo.TYPE_AUDIO, bestTrack); 2201 } else { 2202 mTvOptionsManager.onMultiAudioChanged( 2203 Utils.getMultiAudioString(this, bestTrack, false)); 2204 } 2205 return; 2206 } 2207 mTvOptionsManager.onMultiAudioChanged(null); 2208 } 2209 2210 private void applyClosedCaption() { 2211 List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE); 2212 if (tracks == null) { 2213 mTvOptionsManager.onClosedCaptionsChanged(null); 2214 return; 2215 } 2216 2217 boolean enabled = mCaptionSettings.isEnabled(); 2218 mTvView.setClosedCaptionEnabled(enabled); 2219 2220 String selectedTrackId = getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE); 2221 TvTrackInfo alternativeTrack = null; 2222 if (enabled) { 2223 String language = mCaptionSettings.getLanguage(); 2224 String trackId = mCaptionSettings.getTrackId(); 2225 for (TvTrackInfo track : tracks) { 2226 if (Utils.isEqualLanguage(track.getLanguage(), language)) { 2227 if (track.getId().equals(trackId)) { 2228 if (!track.getId().equals(selectedTrackId)) { 2229 selectTrack(TvTrackInfo.TYPE_SUBTITLE, track); 2230 } else { 2231 // Already selected. Update the option string only. 2232 mTvOptionsManager.onClosedCaptionsChanged(track); 2233 } 2234 if (DEBUG) { 2235 Log.d(TAG, "Subtitle Track Selected {id=" + track.getId() 2236 + ", language=" + track.getLanguage() + "}"); 2237 } 2238 return; 2239 } else if (alternativeTrack == null) { 2240 alternativeTrack = track; 2241 } 2242 } 2243 } 2244 if (alternativeTrack != null) { 2245 if (!alternativeTrack.getId().equals(selectedTrackId)) { 2246 selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack); 2247 } else { 2248 mTvOptionsManager.onClosedCaptionsChanged(alternativeTrack); 2249 } 2250 if (DEBUG) { 2251 Log.d(TAG, "Subtitle Track Selected {id=" + alternativeTrack.getId() 2252 + ", language=" + alternativeTrack.getLanguage() + "}"); 2253 } 2254 return; 2255 } 2256 } 2257 if (selectedTrackId != null) { 2258 selectTrack(TvTrackInfo.TYPE_SUBTITLE, null); 2259 if (DEBUG) Log.d(TAG, "Subtitle Track Unselected"); 2260 return; 2261 } 2262 mTvOptionsManager.onClosedCaptionsChanged(null); 2263 } 2264 2265 /** 2266 * Pops up the KeypadChannelSwitchView with the given key input event. 2267 * 2268 * @param keyCode A key code of the key event. 2269 */ 2270 public void showKeypadChannelSwitchView(int keyCode) { 2271 if (mChannelTuner.areAllChannelsLoaded()) { 2272 mOverlayManager.showKeypadChannelSwitch(); 2273 mKeypadChannelSwitchView.onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0); 2274 } 2275 } 2276 2277 public void showSearchActivity() { 2278 // HACK: Once we moved the window layer to TYPE_APPLICATION_SUB_PANEL, 2279 // the voice button doesn't work. So we directly call the voice action. 2280 SearchManagerHelper.getInstance(this).launchAssistAction(); 2281 } 2282 2283 public void showProgramGuideSearchFragment() { 2284 getFragmentManager().beginTransaction().replace(R.id.fragment_container, mSearchFragment) 2285 .addToBackStack(null).commit(); 2286 } 2287 2288 @Override 2289 protected void onSaveInstanceState(Bundle outState) { 2290 // Do not save instance state because restoring instance state when TV app died 2291 // unexpectedly can cause some problems like initializing fragments duplicately and 2292 // accessing resource before it is initialized. 2293 } 2294 2295 @Override 2296 protected void onDestroy() { 2297 if (DEBUG) Log.d(TAG, "onDestroy()"); 2298 SideFragment.releasePreloadedRecycledViews(); 2299 if (mTvView != null) { 2300 mTvView.release(); 2301 } 2302 if (mPipView != null) { 2303 mPipView.release(); 2304 } 2305 if (mChannelTuner != null) { 2306 mChannelTuner.removeListener(mChannelTunerListener); 2307 mChannelTuner.stop(); 2308 } 2309 TvApplication application = ((TvApplication) getApplication()); 2310 if (mProgramDataManager != null) { 2311 mProgramDataManager.removeOnCurrentProgramUpdatedListener( 2312 Channel.INVALID_ID, mOnCurrentProgramUpdatedListener); 2313 if (application.getMainActivityWrapper().isCurrent(this)) { 2314 mProgramDataManager.setPrefetchEnabled(false); 2315 } 2316 } 2317 if (mPipInputManager != null) { 2318 mPipInputManager.stop(); 2319 } 2320 if (mOverlayManager != null) { 2321 mOverlayManager.release(); 2322 } 2323 if (mKeypadChannelSwitchView != null) { 2324 mKeypadChannelSwitchView.setChannels(null); 2325 } 2326 mMemoryManageables.clear(); 2327 if (mMediaSession != null) { 2328 mMediaSession.release(); 2329 } 2330 if (mAudioCapabilitiesReceiver != null) { 2331 mAudioCapabilitiesReceiver.unregister(); 2332 } 2333 mHandler.removeCallbacksAndMessages(null); 2334 application.getMainActivityWrapper().onMainActivityDestroyed(this); 2335 if (mSendConfigInfoRecurringRunner != null) { 2336 mSendConfigInfoRecurringRunner.stop(); 2337 mSendConfigInfoRecurringRunner = null; 2338 } 2339 if (mChannelStatusRecurringRunner != null) { 2340 mChannelStatusRecurringRunner.stop(); 2341 mChannelStatusRecurringRunner = null; 2342 } 2343 if (mTvInputManagerHelper != null && Features.TUNER.isEnabled(this)) { 2344 mTvInputManagerHelper.removeCallback(mTvInputCallback); 2345 } 2346 super.onDestroy(); 2347 } 2348 2349 @Override 2350 public boolean onKeyDown(int keyCode, KeyEvent event) { 2351 if (SystemProperties.LOG_KEYEVENT.getValue()) { 2352 Log.d(TAG, "onKeyDown(" + keyCode + ", " + event + ")"); 2353 } 2354 switch (mOverlayManager.onKeyDown(keyCode, event)) { 2355 case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY: 2356 return super.onKeyDown(keyCode, event); 2357 case KEY_EVENT_HANDLER_RESULT_HANDLED: 2358 return true; 2359 case KEY_EVENT_HANDLER_RESULT_NOT_HANDLED: 2360 return false; 2361 case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH: 2362 default: 2363 // pass through 2364 } 2365 if (mSearchFragment.isVisible()) { 2366 return super.onKeyDown(keyCode, event); 2367 } 2368 if (!mChannelTuner.areAllChannelsLoaded()) { 2369 return false; 2370 } 2371 if (!mChannelTuner.isCurrentChannelPassthrough()) { 2372 switch (keyCode) { 2373 case KeyEvent.KEYCODE_CHANNEL_UP: 2374 case KeyEvent.KEYCODE_DPAD_UP: 2375 if (event.getRepeatCount() == 0 2376 && mChannelTuner.getBrowsableChannelCount() > 0) { 2377 // message sending should be done before moving channel, because we use the 2378 // existence of message to decide if users are switching channel. 2379 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_UP_PRESSED, 2380 System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS); 2381 moveToAdjacentChannel(true, false); 2382 mTracker.sendChannelUp(); 2383 } 2384 return true; 2385 case KeyEvent.KEYCODE_CHANNEL_DOWN: 2386 case KeyEvent.KEYCODE_DPAD_DOWN: 2387 if (event.getRepeatCount() == 0 2388 && mChannelTuner.getBrowsableChannelCount() > 0) { 2389 // message sending should be done before moving channel, because we use the 2390 // existence of message to decide if users are switching channel. 2391 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_DOWN_PRESSED, 2392 System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS); 2393 moveToAdjacentChannel(false, false); 2394 mTracker.sendChannelDown(); 2395 } 2396 return true; 2397 } 2398 } 2399 return super.onKeyDown(keyCode, event); 2400 } 2401 2402 @Override 2403 public boolean onKeyUp(int keyCode, KeyEvent event) { 2404 /* 2405 * The following keyboard keys map to these remote keys or "debug actions" 2406 * - -------- 2407 * A KEYCODE_MEDIA_AUDIO_TRACK 2408 * D debug: show debug options 2409 * E updateChannelBannerAndShowIfNeeded 2410 * G debug: refresh cloud epg 2411 * I KEYCODE_TV_INPUT 2412 * O debug: show display mode option 2413 * P debug: togglePipView 2414 * S KEYCODE_CAPTIONS: select subtitle 2415 * W debug: toggle screen size 2416 * V KEYCODE_MEDIA_RECORD debug: record the current channel for 30 sec 2417 */ 2418 if (SystemProperties.LOG_KEYEVENT.getValue()) { 2419 Log.d(TAG, "onKeyUp(" + keyCode + ", " + event + ")"); 2420 } 2421 // If we are in the middle of channel change, finish it before showing overlays. 2422 finishChannelChangeIfNeeded(); 2423 2424 if (event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) { 2425 showSearchActivity(); 2426 return true; 2427 } 2428 switch (mOverlayManager.onKeyUp(keyCode, event)) { 2429 case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY: 2430 return super.onKeyUp(keyCode, event); 2431 case KEY_EVENT_HANDLER_RESULT_HANDLED: 2432 return true; 2433 case KEY_EVENT_HANDLER_RESULT_NOT_HANDLED: 2434 return false; 2435 case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH: 2436 default: 2437 // pass through 2438 } 2439 if (mSearchFragment.isVisible()) { 2440 if (keyCode == KeyEvent.KEYCODE_BACK) { 2441 getFragmentManager().popBackStack(); 2442 return true; 2443 } 2444 return super.onKeyUp(keyCode, event); 2445 } 2446 if (keyCode == KeyEvent.KEYCODE_BACK) { 2447 // When the event is from onUnhandledInputEvent, onBackPressed is not automatically 2448 // called. Therefore, we need to explicitly call onBackPressed(). 2449 onBackPressed(); 2450 return true; 2451 } 2452 2453 if (!mChannelTuner.areAllChannelsLoaded()) { 2454 // Now channel map is under loading. 2455 } else if (mChannelTuner.getBrowsableChannelCount() == 0) { 2456 switch (keyCode) { 2457 case KeyEvent.KEYCODE_CHANNEL_UP: 2458 case KeyEvent.KEYCODE_DPAD_UP: 2459 case KeyEvent.KEYCODE_CHANNEL_DOWN: 2460 case KeyEvent.KEYCODE_DPAD_DOWN: 2461 case KeyEvent.KEYCODE_NUMPAD_ENTER: 2462 case KeyEvent.KEYCODE_DPAD_CENTER: 2463 case KeyEvent.KEYCODE_E: 2464 case KeyEvent.KEYCODE_MENU: 2465 showSettingsFragment(); 2466 return true; 2467 } 2468 } else { 2469 if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) { 2470 showKeypadChannelSwitchView(keyCode); 2471 return true; 2472 } 2473 switch (keyCode) { 2474 case KeyEvent.KEYCODE_DPAD_RIGHT: 2475 if (!mTvView.isVideoAvailable() 2476 && mTvView.getVideoUnavailableReason() 2477 == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE) { 2478 DvrUiHelper.startSchedulesActivityForTuneConflict(this, 2479 mChannelTuner.getCurrentChannel()); 2480 return true; 2481 } 2482 if (!PermissionUtils.hasModifyParentalControls(this)) { 2483 // TODO: support this feature for non-system LC app. b/23939816 2484 return true; 2485 } 2486 PinDialogFragment dialog = null; 2487 if (mTvView.isScreenBlocked()) { 2488 dialog = new PinDialogFragment( 2489 PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL, 2490 new PinDialogFragment.ResultListener() { 2491 @Override 2492 public void done(boolean success) { 2493 if (success) { 2494 unblockScreen(mTvView); 2495 mIsCurrentChannelUnblockedByUser = true; 2496 } 2497 } 2498 }); 2499 } else if (mTvView.getBlockedContentRating() != null) { 2500 final TvContentRating rating = mTvView.getBlockedContentRating(); 2501 dialog = new PinDialogFragment( 2502 PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM, 2503 new PinDialogFragment.ResultListener() { 2504 @Override 2505 public void done(boolean success) { 2506 if (success) { 2507 mLastAllowedRatingForCurrentChannel = rating; 2508 mTvView.unblockContent(rating); 2509 } 2510 } 2511 }); 2512 } 2513 if (dialog != null) { 2514 mOverlayManager.showDialogFragment(PinDialogFragment.DIALOG_TAG, dialog, 2515 false); 2516 } 2517 return true; 2518 case KeyEvent.KEYCODE_WINDOW: 2519 enterPictureInPictureMode(); 2520 return true; 2521 case KeyEvent.KEYCODE_ENTER: 2522 case KeyEvent.KEYCODE_NUMPAD_ENTER: 2523 case KeyEvent.KEYCODE_E: 2524 case KeyEvent.KEYCODE_DPAD_CENTER: 2525 case KeyEvent.KEYCODE_MENU: 2526 if (event.isCanceled()) { 2527 // Ignore canceled key. 2528 // Note that if there's a TIS granted RECEIVE_INPUT_EVENT, 2529 // fallback keys not blacklisted will have FLAG_CANCELED. 2530 // See dispatchKeyEvent() for detail. 2531 return true; 2532 } 2533 if (keyCode != KeyEvent.KEYCODE_MENU) { 2534 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); 2535 } 2536 if (keyCode != KeyEvent.KEYCODE_E) { 2537 mOverlayManager.showMenu(Menu.REASON_NONE); 2538 } 2539 return true; 2540 case KeyEvent.KEYCODE_CHANNEL_UP: 2541 case KeyEvent.KEYCODE_DPAD_UP: 2542 case KeyEvent.KEYCODE_CHANNEL_DOWN: 2543 case KeyEvent.KEYCODE_DPAD_DOWN: 2544 // Channel change is already done in the head of this method. 2545 return true; 2546 case KeyEvent.KEYCODE_S: 2547 if (!SystemProperties.USE_DEBUG_KEYS.getValue()) { 2548 break; 2549 } 2550 case KeyEvent.KEYCODE_CAPTIONS: { 2551 mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment()); 2552 return true; 2553 } 2554 case KeyEvent.KEYCODE_A: 2555 if (!SystemProperties.USE_DEBUG_KEYS.getValue()) { 2556 break; 2557 } 2558 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 2559 mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment()); 2560 return true; 2561 } 2562 case KeyEvent.KEYCODE_GUIDE: { 2563 mOverlayManager.showProgramGuide(); 2564 return true; 2565 } 2566 case KeyEvent.KEYCODE_INFO: { 2567 mOverlayManager.showBanner(); 2568 return true; 2569 } 2570 case KeyEvent.KEYCODE_MEDIA_RECORD: 2571 case KeyEvent.KEYCODE_V: { 2572 Channel currentChannel = getCurrentChannel(); 2573 if (currentChannel != null && mDvrManager != null) { 2574 boolean isRecording = 2575 mDvrManager.getCurrentRecording(currentChannel.getId()) != null; 2576 if (!isRecording) { 2577 if (!mDvrManager.isChannelRecordable(currentChannel)) { 2578 Toast.makeText(this, R.string.dvr_msg_cannot_record_program, 2579 Toast.LENGTH_SHORT).show(); 2580 } else { 2581 if (!DvrUiHelper.checkStorageStatusAndShowErrorMessage(this, 2582 currentChannel.getInputId())) { 2583 return true; 2584 } 2585 Program program = mProgramDataManager 2586 .getCurrentProgram(currentChannel.getId()); 2587 if (program == null) { 2588 DvrUiHelper 2589 .showChannelRecordDurationOptions(this, currentChannel); 2590 } else if (DvrUiHelper.handleCreateSchedule(this, program)) { 2591 String msg = getString( 2592 R.string.dvr_msg_current_program_scheduled, 2593 program.getTitle(), Utils.toTimeString( 2594 program.getEndTimeUtcMillis(), false)); 2595 Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); 2596 } 2597 } 2598 } else { 2599 DvrUiHelper.showStopRecordingDialog(this, currentChannel.getId(), 2600 DvrStopRecordingFragment.REASON_USER_STOP, 2601 new HalfSizedDialogFragment.OnActionClickListener() { 2602 @Override 2603 public void onActionClick(long actionId) { 2604 if (actionId == DvrStopRecordingFragment.ACTION_STOP) { 2605 ScheduledRecording currentRecording = 2606 mDvrManager.getCurrentRecording( 2607 currentChannel.getId()); 2608 if (currentRecording != null) { 2609 mDvrManager.stopRecording(currentRecording); 2610 } 2611 } 2612 } 2613 }); 2614 } 2615 } 2616 return true; 2617 } 2618 } 2619 } 2620 if (keyCode == KeyEvent.KEYCODE_WINDOW) { 2621 // Consumes the PIP button to prevent entering PIP mode 2622 // in case that TV isn't showing properly (e.g. no browsable channel) 2623 return true; 2624 } 2625 if (SystemProperties.USE_DEBUG_KEYS.getValue() || BuildConfig.ENG) { 2626 switch (keyCode) { 2627 case KeyEvent.KEYCODE_W: { 2628 mDebugNonFullSizeScreen = !mDebugNonFullSizeScreen; 2629 if (mDebugNonFullSizeScreen) { 2630 FrameLayout.LayoutParams params = 2631 (FrameLayout.LayoutParams) mTvView.getLayoutParams(); 2632 params.width = 960; 2633 params.height = 540; 2634 params.gravity = Gravity.START; 2635 mTvView.setLayoutParams(params); 2636 } else { 2637 FrameLayout.LayoutParams params = 2638 (FrameLayout.LayoutParams) mTvView.getLayoutParams(); 2639 params.width = ViewGroup.LayoutParams.MATCH_PARENT; 2640 params.height = ViewGroup.LayoutParams.MATCH_PARENT; 2641 params.gravity = Gravity.CENTER; 2642 mTvView.setLayoutParams(params); 2643 } 2644 return true; 2645 } 2646 case KeyEvent.KEYCODE_P: { 2647 togglePipView(); 2648 return true; 2649 } 2650 case KeyEvent.KEYCODE_CTRL_LEFT: 2651 case KeyEvent.KEYCODE_CTRL_RIGHT: { 2652 mUseKeycodeBlacklist = !mUseKeycodeBlacklist; 2653 return true; 2654 } 2655 case KeyEvent.KEYCODE_O: { 2656 mOverlayManager.getSideFragmentManager().show(new DisplayModeFragment()); 2657 return true; 2658 } 2659 case KeyEvent.KEYCODE_D: 2660 mOverlayManager.getSideFragmentManager().show(new DeveloperOptionFragment()); 2661 return true; 2662 } 2663 } 2664 return super.onKeyUp(keyCode, event); 2665 } 2666 2667 @Override 2668 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 2669 if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "onKeyLongPress(" + event); 2670 if (USE_BACK_KEY_LONG_PRESS) { 2671 // Treat the BACK key long press as the normal press since we changed the behavior in 2672 // onBackPressed(). 2673 if (keyCode == KeyEvent.KEYCODE_BACK) { 2674 // It takes long time for TV app to finish, so stop TV first. 2675 stopAll(false); 2676 super.onBackPressed(); 2677 return true; 2678 } 2679 } 2680 return false; 2681 } 2682 2683 @Override 2684 public void onBackPressed() { 2685 // The activity should be returned to the caller of this activity 2686 // when the mSource is not null. 2687 if (!mOverlayManager.getSideFragmentManager().isActive() && isPlaying() 2688 && mSource == null) { 2689 // If back key would exit TV app, 2690 // show McLauncher instead so we can get benefit of McLauncher's shyMode. 2691 Intent startMain = new Intent(Intent.ACTION_MAIN); 2692 startMain.addCategory(Intent.CATEGORY_HOME); 2693 startActivity(startMain); 2694 } else { 2695 super.onBackPressed(); 2696 } 2697 } 2698 2699 @Override 2700 public void onUserInteraction() { 2701 super.onUserInteraction(); 2702 if (mOverlayManager != null) { 2703 mOverlayManager.onUserInteraction(); 2704 } 2705 } 2706 2707 @Override 2708 public void enterPictureInPictureMode() { 2709 // We need to hide overlay first, before moving the activity to PIP. If not, UI will 2710 // be shown during PIP stack resizing, because UI and its animation is stuck during 2711 // PIP resizing. 2712 mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION); 2713 mHandler.post(new Runnable() { 2714 @Override 2715 public void run() { 2716 MainActivity.super.enterPictureInPictureMode(); 2717 } 2718 }); 2719 } 2720 2721 @Override 2722 public void onWindowFocusChanged(boolean hasFocus) { 2723 if (!hasFocus) { 2724 finishChannelChangeIfNeeded(); 2725 } 2726 } 2727 2728 public void togglePipView() { 2729 enablePipView(!mPipEnabled, true); 2730 mOverlayManager.getMenu().update(); 2731 } 2732 2733 public boolean isPipEnabled() { 2734 return mPipEnabled; 2735 } 2736 2737 public void tuneToChannelForPip(Channel channel) { 2738 if (!mPipEnabled) { 2739 throw new IllegalStateException("tuneToChannelForPip is called when PIP is off"); 2740 } 2741 if (mPipChannel.equals(channel)) { 2742 return; 2743 } 2744 mPipChannel = channel; 2745 startPip(true); 2746 } 2747 2748 private void enablePipView(boolean enable, boolean fromUserInteraction) { 2749 if (enable == mPipEnabled) { 2750 return; 2751 } 2752 if (enable) { 2753 List<PipInput> pipAvailableInputs = mPipInputManager.getPipInputList(true); 2754 if (pipAvailableInputs.isEmpty()) { 2755 Toast.makeText(this, R.string.msg_no_available_input_by_pip, Toast.LENGTH_SHORT) 2756 .show(); 2757 return; 2758 } 2759 // TODO: choose the last pip input. 2760 Channel pipChannel = pipAvailableInputs.get(0).getChannel(); 2761 if (pipChannel != null) { 2762 mPipEnabled = true; 2763 mPipChannel = pipChannel; 2764 startPip(fromUserInteraction); 2765 mTvViewUiManager.restorePipSize(); 2766 mTvViewUiManager.restorePipLayout(); 2767 mTvOptionsManager.onPipChanged(mPipEnabled); 2768 } else { 2769 Toast.makeText(this, R.string.msg_no_available_input_by_pip, Toast.LENGTH_SHORT) 2770 .show(); 2771 } 2772 } else { 2773 mPipEnabled = false; 2774 mPipChannel = null; 2775 // Recover the stream volume of the main TV view, if needed. 2776 if (mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW) { 2777 setVolumeByAudioFocusStatus(mTvView); 2778 mPipSound = TvSettings.PIP_SOUND_MAIN; 2779 mTvOptionsManager.onPipSoundChanged(mPipSound); 2780 } 2781 stopPip(); 2782 mTvViewUiManager.restoreDisplayMode(false); 2783 mTvOptionsManager.onPipChanged(mPipEnabled); 2784 } 2785 } 2786 2787 private boolean isChannelChangeKeyDownReceived() { 2788 return mHandler.hasMessages(MSG_CHANNEL_UP_PRESSED) 2789 || mHandler.hasMessages(MSG_CHANNEL_DOWN_PRESSED); 2790 } 2791 2792 private void finishChannelChangeIfNeeded() { 2793 if (!isChannelChangeKeyDownReceived()) { 2794 return; 2795 } 2796 mHandler.removeMessages(MSG_CHANNEL_UP_PRESSED); 2797 mHandler.removeMessages(MSG_CHANNEL_DOWN_PRESSED); 2798 if (mChannelTuner.getBrowsableChannelCount() > 0) { 2799 if (!mTvView.isPlaying()) { 2800 // We expect that mTvView is already played. But, it is sometimes not. 2801 // TODO: we figure out the reason when mTvView is not played. 2802 Log.w(TAG, "TV view isn't played in finishChannelChangeIfNeeded"); 2803 } 2804 tuneToChannel(mChannelTuner.getCurrentChannel()); 2805 } else { 2806 showSettingsFragment(); 2807 } 2808 } 2809 2810 private boolean dispatchKeyEventToSession(final KeyEvent event) { 2811 if (SystemProperties.LOG_KEYEVENT.getValue()) { 2812 Log.d(TAG, "dispatchKeyEventToSession(" + event + ")"); 2813 } 2814 if (mPipEnabled && mChannelTuner.isCurrentChannelPassthrough()) { 2815 // If PIP is enabled, key events will be used by UI. 2816 return false; 2817 } 2818 boolean handled = false; 2819 if (mTvView != null) { 2820 handled = mTvView.dispatchKeyEvent(event); 2821 } 2822 if (isKeyEventBlocked()) { 2823 if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK 2824 || event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B) && mNeedShowBackKeyGuide) { 2825 // KeyEvent.KEYCODE_BUTTON_B is also used like the back button. 2826 Toast.makeText(this, R.string.msg_back_key_guide, Toast.LENGTH_SHORT).show(); 2827 mNeedShowBackKeyGuide = false; 2828 } 2829 return true; 2830 } 2831 return handled; 2832 } 2833 2834 private boolean isKeyEventBlocked() { 2835 // If the current channel is passthrough channel without a PIP view, 2836 // we always don't handle the key events in TV activity. Instead, the key event will 2837 // be handled by the passthrough TV input. 2838 return mChannelTuner.isCurrentChannelPassthrough() && !mPipEnabled; 2839 } 2840 2841 private void tuneToLastWatchedChannelForTunerInput() { 2842 if (!mChannelTuner.isCurrentChannelPassthrough()) { 2843 return; 2844 } 2845 if (mPipEnabled) { 2846 if (!mPipChannel.isPassthrough()) { 2847 enablePipView(false, true); 2848 } 2849 } 2850 stopTv(); 2851 startTv(null); 2852 } 2853 2854 public void tuneToChannel(Channel channel) { 2855 if (channel == null) { 2856 if (mTvView.isPlaying()) { 2857 mTvView.reset(); 2858 } 2859 } else { 2860 if (mPipEnabled && mPipInputManager.areInSamePipInput(channel, mPipChannel)) { 2861 enablePipView(false, true); 2862 } 2863 if (!mTvView.isPlaying()) { 2864 startTv(channel.getUri()); 2865 } else if (channel.equals(mTvView.getCurrentChannel())) { 2866 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE); 2867 } else if (mChannelTuner.moveToChannel(channel)) { 2868 // Channel banner would be updated inside of tune. 2869 tune(); 2870 } else { 2871 showSettingsFragment(); 2872 } 2873 } 2874 } 2875 2876 /** 2877 * This method just moves the channel in the channel map and updates the channel banner, 2878 * but doesn't actually tune to the channel. 2879 * The caller of this method should call {@link #tune} in the end. 2880 * 2881 * @param channelUp {@code true} for channel up, and {@code false} for channel down. 2882 * @param fastTuning {@code true} if fast tuning is requested. 2883 */ 2884 private void moveToAdjacentChannel(boolean channelUp, boolean fastTuning) { 2885 if (mChannelTuner.moveToAdjacentBrowsableChannel(channelUp)) { 2886 updateChannelBannerAndShowIfNeeded(fastTuning ? UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST 2887 : UPDATE_CHANNEL_BANNER_REASON_TUNE); 2888 } 2889 } 2890 2891 public Channel getPipChannel() { 2892 return mPipChannel; 2893 } 2894 2895 /** 2896 * Swap the main and the sub screens while in the PIP mode. 2897 */ 2898 public void swapPip() { 2899 if (!mPipEnabled || mTvView == null || mPipView == null) { 2900 Log.e(TAG, "swapPip() - not in PIP"); 2901 mPipSwap = false; 2902 return; 2903 } 2904 2905 Channel channel = mTvView.getCurrentChannel(); 2906 boolean tvViewBlocked = mTvView.isScreenBlocked(); 2907 boolean pipViewBlocked = mPipView.isScreenBlocked(); 2908 if (channel == null || !mTvView.isPlaying()) { 2909 // If the TV view is not currently playing or its current channel is null, swapping here 2910 // basically means disabling the PIP mode and getting back to the full screen since 2911 // there's no point of keeping a blank PIP screen at the bottom which is not tune-able. 2912 enablePipView(false, true); 2913 mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_DEFAULT); 2914 mPipSwap = false; 2915 return; 2916 } 2917 2918 // Reset the TV view and tune the PIP view to the previous channel of the TV view. 2919 mTvView.reset(); 2920 mPipView.reset(); 2921 Channel oldPipChannel = mPipChannel; 2922 tuneToChannelForPip(channel); 2923 if (tvViewBlocked) { 2924 mPipView.blockScreen(); 2925 } else { 2926 mPipView.unblockScreen(); 2927 } 2928 2929 if (oldPipChannel != null) { 2930 // Tune the TV view to the previous PIP channel. 2931 tuneToChannel(oldPipChannel); 2932 } 2933 if (pipViewBlocked) { 2934 mTvView.blockScreen(); 2935 } else { 2936 mTvView.unblockScreen(); 2937 } 2938 if (mPipSound == TvSettings.PIP_SOUND_MAIN) { 2939 setVolumeByAudioFocusStatus(mTvView); 2940 } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW 2941 setVolumeByAudioFocusStatus(mPipView); 2942 } 2943 mPipSwap = !mPipSwap; 2944 mTvOptionsManager.onPipSwapChanged(mPipSwap); 2945 } 2946 2947 /** 2948 * Toggle where the sound is coming from when the user is watching the PIP. 2949 */ 2950 public void togglePipSoundMode() { 2951 if (!mPipEnabled || mTvView == null || mPipView == null) { 2952 Log.e(TAG, "togglePipSoundMode() - not in PIP"); 2953 return; 2954 } 2955 if (mPipSound == TvSettings.PIP_SOUND_MAIN) { 2956 setVolumeByAudioFocusStatus(mPipView); 2957 mPipSound = TvSettings.PIP_SOUND_PIP_WINDOW; 2958 } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW 2959 setVolumeByAudioFocusStatus(mTvView); 2960 mPipSound = TvSettings.PIP_SOUND_MAIN; 2961 } 2962 restoreMainTvView(); 2963 mTvOptionsManager.onPipSoundChanged(mPipSound); 2964 } 2965 2966 /** 2967 * Set the main TV view which holds HDMI-CEC active source based on the sound mode 2968 */ 2969 private void restoreMainTvView() { 2970 if (mPipSound == TvSettings.PIP_SOUND_MAIN) { 2971 mTvView.setMain(); 2972 } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW 2973 mPipView.setMain(); 2974 } 2975 } 2976 2977 @Override 2978 public void onVisibleBehindCanceled() { 2979 stopTv("onVisibleBehindCanceled()", false); 2980 mTracker.sendScreenView(""); 2981 mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; 2982 mAudioManager.abandonAudioFocus(this); 2983 if (mMediaSession.isActive()) { 2984 mMediaSession.setActive(false); 2985 } 2986 stopPip(); 2987 mVisibleBehind = false; 2988 if (!mOtherActivityLaunched && Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { 2989 // Workaround: in M, onStop is not called, even though it should be called after 2990 // onVisibleBehindCanceled is called. As a workaround, we call finish(). 2991 finish(); 2992 } 2993 super.onVisibleBehindCanceled(); 2994 } 2995 2996 @Override 2997 public void startActivityForResult(Intent intent, int requestCode) { 2998 mOtherActivityLaunched = true; 2999 if (intent.getCategories() == null 3000 || !intent.getCategories().contains(Intent.CATEGORY_HOME)) { 3001 // Workaround b/30150267 3002 requestVisibleBehind(false); 3003 } 3004 super.startActivityForResult(intent, requestCode); 3005 } 3006 3007 public List<TvTrackInfo> getTracks(int type) { 3008 return mTvView.getTracks(type); 3009 } 3010 3011 public String getSelectedTrack(int type) { 3012 return mTvView.getSelectedTrack(type); 3013 } 3014 3015 private void selectTrack(int type, TvTrackInfo track) { 3016 mTvView.selectTrack(type, track == null ? null : track.getId()); 3017 if (type == TvTrackInfo.TYPE_AUDIO) { 3018 mTvOptionsManager.onMultiAudioChanged(track == null ? null : 3019 Utils.getMultiAudioString(this, track, false)); 3020 } else if (type == TvTrackInfo.TYPE_SUBTITLE) { 3021 mTvOptionsManager.onClosedCaptionsChanged(track); 3022 } 3023 } 3024 3025 public void selectAudioTrack(String trackId) { 3026 saveMultiAudioSetting(trackId); 3027 applyMultiAudio(); 3028 } 3029 3030 private void saveMultiAudioSetting(String trackId) { 3031 List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO); 3032 if (tracks != null) { 3033 for (TvTrackInfo track : tracks) { 3034 if (track.getId().equals(trackId)) { 3035 TvSettings.setMultiAudioId(this, track.getId()); 3036 TvSettings.setMultiAudioLanguage(this, track.getLanguage()); 3037 TvSettings.setMultiAudioChannelCount(this, track.getAudioChannelCount()); 3038 return; 3039 } 3040 } 3041 } 3042 TvSettings.setMultiAudioId(this, null); 3043 TvSettings.setMultiAudioLanguage(this, null); 3044 TvSettings.setMultiAudioChannelCount(this, 0); 3045 } 3046 3047 public void selectSubtitleTrack(int option, String trackId) { 3048 saveClosedCaptionSetting(option, trackId); 3049 applyClosedCaption(); 3050 } 3051 3052 public void selectSubtitleLanguage(int option, String language, String trackId) { 3053 mCaptionSettings.setEnableOption(option); 3054 mCaptionSettings.setLanguage(language); 3055 mCaptionSettings.setTrackId(trackId); 3056 applyClosedCaption(); 3057 } 3058 3059 private void saveClosedCaptionSetting(int option, String trackId) { 3060 mCaptionSettings.setEnableOption(option); 3061 if (option == CaptionSettings.OPTION_ON) { 3062 List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE); 3063 if (tracks != null) { 3064 for (TvTrackInfo track : tracks) { 3065 if (track.getId().equals(trackId)) { 3066 mCaptionSettings.setLanguage(track.getLanguage()); 3067 mCaptionSettings.setTrackId(trackId); 3068 return; 3069 } 3070 } 3071 } 3072 } 3073 } 3074 3075 private void updateAvailabilityToast() { 3076 updateAvailabilityToast(mTvView); 3077 } 3078 3079 private void updateAvailabilityToast(StreamInfo info) { 3080 if (info.isVideoAvailable()) { 3081 return; 3082 } 3083 3084 int stringId; 3085 switch (info.getVideoUnavailableReason()) { 3086 case TunableTvView.VIDEO_UNAVAILABLE_REASON_NOT_TUNED: 3087 case TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE: 3088 case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING: 3089 case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: 3090 case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY: 3091 case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: 3092 return; 3093 case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: 3094 default: 3095 stringId = R.string.msg_channel_unavailable_unknown; 3096 break; 3097 } 3098 3099 Toast.makeText(this, stringId, Toast.LENGTH_SHORT).show(); 3100 } 3101 3102 public ParentalControlSettings getParentalControlSettings() { 3103 return mTvInputManagerHelper.getParentalControlSettings(); 3104 } 3105 3106 /** 3107 * Returns a ContentRatingsManager instance. 3108 */ 3109 public ContentRatingsManager getContentRatingsManager() { 3110 return mTvInputManagerHelper.getContentRatingsManager(); 3111 } 3112 3113 public CaptionSettings getCaptionSettings() { 3114 return mCaptionSettings; 3115 } 3116 3117 /** 3118 * Adds the {@link OnActionClickListener}. 3119 */ 3120 public void addOnActionClickListener(OnActionClickListener listener) { 3121 mOnActionClickListeners.add(listener); 3122 } 3123 3124 /** 3125 * Removes the {@link OnActionClickListener}. 3126 */ 3127 public void removeOnActionClickListener(OnActionClickListener listener) { 3128 mOnActionClickListeners.remove(listener); 3129 } 3130 3131 @Override 3132 public boolean onActionClick(String category, int actionId, Bundle params) { 3133 // There should be only one action listener per an action. 3134 for (OnActionClickListener l : mOnActionClickListeners) { 3135 if (l.onActionClick(category, actionId, params)) { 3136 return true; 3137 } 3138 } 3139 return false; 3140 } 3141 3142 // Initialize TV app for test. The setup process should be finished before the Live TV app is 3143 // started. We only enable all the channels here. 3144 private void initForTest() { 3145 if (!TvCommonUtils.isRunningInTest()) { 3146 return; 3147 } 3148 3149 Utils.enableAllChannels(this); 3150 } 3151 3152 // Lazy initialization 3153 private void lazyInitializeIfNeeded() { 3154 // Already initialized. 3155 if (mLazyInitialized) { 3156 return; 3157 } 3158 mLazyInitialized = true; 3159 // Running initialization. 3160 mHandler.postDelayed(new Runnable() { 3161 @Override 3162 public void run() { 3163 if (mActivityStarted) { 3164 initAnimations(); 3165 initSideFragments(); 3166 } 3167 } 3168 }, LAZY_INITIALIZATION_DELAY); 3169 } 3170 3171 private void initAnimations() { 3172 mTvViewUiManager.initAnimatorIfNeeded(); 3173 mOverlayManager.initAnimatorIfNeeded(); 3174 } 3175 3176 private void initSideFragments() { 3177 SideFragment.preloadRecycledViews(this); 3178 } 3179 3180 @Override 3181 public void onTrimMemory(int level) { 3182 super.onTrimMemory(level); 3183 for (MemoryManageable memoryManageable : mMemoryManageables) { 3184 memoryManageable.performTrimMemory(level); 3185 } 3186 } 3187 3188 private static class MainActivityHandler extends WeakHandler<MainActivity> { 3189 MainActivityHandler(MainActivity mainActivity) { 3190 super(mainActivity); 3191 } 3192 3193 @Override 3194 protected void handleMessage(Message msg, @NonNull MainActivity mainActivity) { 3195 switch (msg.what) { 3196 case MSG_CHANNEL_DOWN_PRESSED: 3197 long startTime = (Long) msg.obj; 3198 // message re-sending should be done before moving channel, because we use the 3199 // existence of message to decide if users are switching channel. 3200 sendMessageDelayed(Message.obtain(msg), getDelay(startTime)); 3201 mainActivity.moveToAdjacentChannel(false, true); 3202 break; 3203 case MSG_CHANNEL_UP_PRESSED: 3204 startTime = (Long) msg.obj; 3205 // message re-sending should be done before moving channel, because we use the 3206 // existence of message to decide if users are switching channel. 3207 sendMessageDelayed(Message.obtain(msg), getDelay(startTime)); 3208 mainActivity.moveToAdjacentChannel(true, true); 3209 break; 3210 case MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE: 3211 mainActivity.updateChannelBannerAndShowIfNeeded( 3212 UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); 3213 break; 3214 } 3215 } 3216 3217 private long getDelay(long startTime) { 3218 if (System.currentTimeMillis() - startTime > CHANNEL_CHANGE_NORMAL_SPEED_DURATION_MS) { 3219 return CHANNEL_CHANGE_DELAY_MS_IN_MAX_SPEED; 3220 } 3221 return CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED; 3222 } 3223 } 3224 3225 private class MyOnTuneListener implements OnTuneListener { 3226 boolean mUnlockAllowedRatingBeforeShrunken = true; 3227 boolean mWasUnderShrunkenTvView; 3228 long mStreamInfoUpdateTimeThresholdMs; 3229 Channel mChannel; 3230 3231 public MyOnTuneListener() { } 3232 3233 private void onTune(Channel channel, boolean wasUnderShrukenTvView) { 3234 mStreamInfoUpdateTimeThresholdMs = 3235 System.currentTimeMillis() + FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS; 3236 mChannel = channel; 3237 mWasUnderShrunkenTvView = wasUnderShrukenTvView; 3238 } 3239 3240 @Override 3241 public void onUnexpectedStop(Channel channel) { 3242 stopTv(); 3243 startTv(null); 3244 } 3245 3246 @Override 3247 public void onTuneFailed(Channel channel) { 3248 Log.w(TAG, "onTuneFailed(" + channel + ")"); 3249 if (mTvView.isFadedOut()) { 3250 mTvView.removeFadeEffect(); 3251 } 3252 // TODO: show something to user about this error. 3253 } 3254 3255 @Override 3256 public void onStreamInfoChanged(StreamInfo info) { 3257 if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) { 3258 mTracker.sendChannelTuneTime(info.getCurrentChannel(), 3259 mTuneDurationTimer.reset()); 3260 } 3261 // If updateChannelBanner() is called without delay, the stream info seems flickering 3262 // when the channel is quickly changed. 3263 if (!mHandler.hasMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE) 3264 && info.isVideoAvailable()) { 3265 if (System.currentTimeMillis() > mStreamInfoUpdateTimeThresholdMs) { 3266 updateChannelBannerAndShowIfNeeded( 3267 UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); 3268 } else { 3269 mHandler.sendMessageDelayed(mHandler.obtainMessage( 3270 MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE), 3271 mStreamInfoUpdateTimeThresholdMs - System.currentTimeMillis()); 3272 } 3273 } 3274 3275 applyDisplayRefreshRate(info.getVideoFrameRate()); 3276 mTvViewUiManager.updateTvView(); 3277 applyMultiAudio(); 3278 applyClosedCaption(); 3279 // TODO: Send command to TIS with checking the settings in TV and CaptionManager. 3280 mOverlayManager.getMenu().onStreamInfoChanged(); 3281 if (mTvView.isVideoAvailable()) { 3282 mTvViewUiManager.fadeInTvView(); 3283 } 3284 mHandler.removeCallbacks(mRestoreMainViewRunnable); 3285 restoreMainTvView(); 3286 } 3287 3288 @Override 3289 public void onChannelRetuned(Uri channel) { 3290 if (channel == null) { 3291 return; 3292 } 3293 Channel currentChannel = 3294 mChannelDataManager.getChannel(ContentUris.parseId(channel)); 3295 if (currentChannel == null) { 3296 Log.e(TAG, "onChannelRetuned is called but can't find a channel with the URI " 3297 + channel); 3298 return; 3299 } 3300 if (isChannelChangeKeyDownReceived()) { 3301 // Ignore this message if the user is changing the channel. 3302 return; 3303 } 3304 mChannelTuner.setCurrentChannel(currentChannel); 3305 mTvView.setCurrentChannel(currentChannel); 3306 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE); 3307 } 3308 3309 @Override 3310 public void onContentBlocked() { 3311 mTuneDurationTimer.reset(); 3312 TvContentRating rating = mTvView.getBlockedContentRating(); 3313 // When tuneTo was called while TV view was shrunken, if the channel id is the same 3314 // with the channel watched before shrunken, we allow the rating which was allowed 3315 // before. 3316 if (mWasUnderShrunkenTvView && mUnlockAllowedRatingBeforeShrunken 3317 && mChannelBeforeShrunkenTvView.equals(mChannel) 3318 && rating.equals(mAllowedRatingBeforeShrunken)) { 3319 mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView(); 3320 mTvView.unblockContent(rating); 3321 } 3322 mChannelBannerView.setBlockingContentRating(rating); 3323 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); 3324 mTvViewUiManager.fadeInTvView(); 3325 } 3326 3327 @Override 3328 public void onContentAllowed() { 3329 if (!isUnderShrunkenTvView()) { 3330 mUnlockAllowedRatingBeforeShrunken = false; 3331 } 3332 mChannelBannerView.setBlockingContentRating(null); 3333 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); 3334 } 3335 } 3336 } 3337