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