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