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