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