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