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