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