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