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