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