1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.ui; 18 19 import android.app.Fragment; 20 import android.app.FragmentManager; 21 import android.app.FragmentManager.OnBackStackChangedListener; 22 import android.content.Intent; 23 import android.media.tv.TvContentRating; 24 import android.media.tv.TvInputInfo; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Message; 28 import android.support.annotation.IntDef; 29 import android.support.annotation.NonNull; 30 import android.support.annotation.UiThread; 31 import android.util.Log; 32 import android.view.Gravity; 33 import android.view.KeyEvent; 34 import android.view.ViewGroup; 35 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; 36 37 import com.android.tv.ChannelTuner; 38 import com.android.tv.MainActivity; 39 import com.android.tv.MainActivity.KeyHandlerResultType; 40 import com.android.tv.R; 41 import com.android.tv.TimeShiftManager; 42 import com.android.tv.TvOptionsManager; 43 import com.android.tv.TvSingletons; 44 import com.android.tv.analytics.Tracker; 45 import com.android.tv.common.WeakHandler; 46 import com.android.tv.common.feature.CommonFeatures; 47 import com.android.tv.common.ui.setup.OnActionClickListener; 48 import com.android.tv.common.ui.setup.SetupFragment; 49 import com.android.tv.common.ui.setup.SetupMultiPaneFragment; 50 import com.android.tv.data.ChannelDataManager; 51 import com.android.tv.data.ProgramDataManager; 52 import com.android.tv.dialog.DvrHistoryDialogFragment; 53 import com.android.tv.dialog.FullscreenDialogFragment; 54 import com.android.tv.dialog.HalfSizedDialogFragment; 55 import com.android.tv.dialog.PinDialogFragment; 56 import com.android.tv.dialog.RecentlyWatchedDialogFragment; 57 import com.android.tv.dialog.SafeDismissDialogFragment; 58 import com.android.tv.dvr.DvrDataManager; 59 import com.android.tv.dvr.ui.browse.DvrBrowseActivity; 60 import com.android.tv.guide.ProgramGuide; 61 import com.android.tv.license.LicenseDialogFragment; 62 import com.android.tv.menu.Menu; 63 import com.android.tv.menu.Menu.MenuShowReason; 64 import com.android.tv.menu.MenuRowFactory; 65 import com.android.tv.menu.MenuView; 66 import com.android.tv.menu.TvOptionsRowAdapter; 67 import com.android.tv.onboarding.NewSourcesFragment; 68 import com.android.tv.onboarding.SetupSourcesFragment; 69 import com.android.tv.search.ProgramGuideSearchFragment; 70 import com.android.tv.ui.TvTransitionManager.SceneType; 71 import com.android.tv.ui.sidepanel.SideFragmentManager; 72 import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment; 73 import com.android.tv.util.TvInputManagerHelper; 74 75 import com.google.auto.factory.AutoFactory; 76 import com.google.auto.factory.Provided; 77 78 import java.lang.annotation.Retention; 79 import java.lang.annotation.RetentionPolicy; 80 import java.util.ArrayList; 81 import java.util.HashSet; 82 import java.util.LinkedList; 83 import java.util.List; 84 import java.util.Queue; 85 import java.util.Set; 86 87 /** A class responsible for the life cycle and event handling of the pop-ups over TV view. */ 88 @UiThread 89 @AutoFactory 90 public class TvOverlayManager implements AccessibilityStateChangeListener { 91 private static final String TAG = "TvOverlayManager"; 92 private static final boolean DEBUG = false; 93 private static final String INTRO_TRACKER_LABEL = "Intro dialog"; 94 95 @Retention(RetentionPolicy.SOURCE) 96 @IntDef( 97 flag = true, 98 value = { 99 FLAG_HIDE_OVERLAYS_DEFAULT, 100 FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION, 101 FLAG_HIDE_OVERLAYS_KEEP_SCENE, 102 FLAG_HIDE_OVERLAYS_KEEP_DIALOG, 103 FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS, 104 FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY, 105 FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE, 106 FLAG_HIDE_OVERLAYS_KEEP_MENU, 107 FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT 108 }) 109 private @interface HideOverlayFlag {} 110 // FLAG_HIDE_OVERLAYs must be bitwise exclusive. 111 public static final int FLAG_HIDE_OVERLAYS_DEFAULT = 0b000000000; 112 public static final int FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION = 0b000000010; 113 public static final int FLAG_HIDE_OVERLAYS_KEEP_SCENE = 0b000000100; 114 public static final int FLAG_HIDE_OVERLAYS_KEEP_DIALOG = 0b000001000; 115 public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS = 0b000010000; 116 public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY = 0b000100000; 117 public static final int FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE = 0b001000000; 118 public static final int FLAG_HIDE_OVERLAYS_KEEP_MENU = 0b010000000; 119 public static final int FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT = 0b100000000; 120 121 private static final int MSG_OVERLAY_CLOSED = 1000; 122 123 @Retention(RetentionPolicy.SOURCE) 124 @IntDef( 125 flag = true, 126 value = { 127 OVERLAY_TYPE_NONE, 128 OVERLAY_TYPE_MENU, 129 OVERLAY_TYPE_SIDE_FRAGMENT, 130 OVERLAY_TYPE_DIALOG, 131 OVERLAY_TYPE_GUIDE, 132 OVERLAY_TYPE_SCENE_CHANNEL_BANNER, 133 OVERLAY_TYPE_SCENE_INPUT_BANNER, 134 OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH, 135 OVERLAY_TYPE_SCENE_SELECT_INPUT, 136 OVERLAY_TYPE_FRAGMENT 137 }) 138 private @interface TvOverlayType {} 139 // OVERLAY_TYPEs must be bitwise exclusive. 140 /** The overlay type which indicates that there are no overlays. */ 141 private static final int OVERLAY_TYPE_NONE = 0b000000000; 142 /** The overlay type for menu. */ 143 private static final int OVERLAY_TYPE_MENU = 0b000000001; 144 /** The overlay type for the side fragment. */ 145 private static final int OVERLAY_TYPE_SIDE_FRAGMENT = 0b000000010; 146 /** The overlay type for dialog fragment. */ 147 private static final int OVERLAY_TYPE_DIALOG = 0b000000100; 148 /** The overlay type for program guide. */ 149 private static final int OVERLAY_TYPE_GUIDE = 0b000001000; 150 /** The overlay type for channel banner. */ 151 private static final int OVERLAY_TYPE_SCENE_CHANNEL_BANNER = 0b000010000; 152 /** The overlay type for input banner. */ 153 private static final int OVERLAY_TYPE_SCENE_INPUT_BANNER = 0b000100000; 154 /** The overlay type for keypad channel switch view. */ 155 private static final int OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH = 0b001000000; 156 /** The overlay type for select input view. */ 157 private static final int OVERLAY_TYPE_SCENE_SELECT_INPUT = 0b010000000; 158 /** The overlay type for fragment other than the side fragment and dialog fragment. */ 159 private static final int OVERLAY_TYPE_FRAGMENT = 0b100000000; 160 // Used for the padded print of the overlay type. 161 private static final int NUM_OVERLAY_TYPES = 9; 162 163 @Retention(RetentionPolicy.SOURCE) 164 @IntDef({ 165 UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW, 166 UPDATE_CHANNEL_BANNER_REASON_TUNE, 167 UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST, 168 UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO, 169 UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK, 170 UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO 171 }) 172 private @interface ChannelBannerUpdateReason {} 173 /** Updates channel banner because the channel banner is forced to show. */ 174 public static final int UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW = 1; 175 /** Updates channel banner because of tuning. */ 176 public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE = 2; 177 /** Updates channel banner because of fast tuning. */ 178 public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST = 3; 179 /** Updates channel banner because of info updating. */ 180 public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO = 4; 181 /** Updates channel banner because the current watched channel is locked or unlocked. */ 182 public static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5; 183 /** Updates channel banner because of stream info updating. */ 184 public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO = 6; 185 /** Updates channel banner because of channel signal updating. */ 186 public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_SIGNAL_STRENGTH = 7; 187 188 private static final String FRAGMENT_TAG_SETUP_SOURCES = "tag_setup_sources"; 189 private static final String FRAGMENT_TAG_NEW_SOURCES = "tag_new_sources"; 190 191 private static final Set<String> AVAILABLE_DIALOG_TAGS = new HashSet<>(); 192 193 static { 194 AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG); 195 AVAILABLE_DIALOG_TAGS.add(DvrHistoryDialogFragment.DIALOG_TAG); 196 AVAILABLE_DIALOG_TAGS.add(PinDialogFragment.DIALOG_TAG); 197 AVAILABLE_DIALOG_TAGS.add(FullscreenDialogFragment.DIALOG_TAG); 198 AVAILABLE_DIALOG_TAGS.add(LicenseDialogFragment.DIALOG_TAG); 199 AVAILABLE_DIALOG_TAGS.add(RatingsFragment.AttributionItem.DIALOG_TAG); 200 AVAILABLE_DIALOG_TAGS.add(HalfSizedDialogFragment.DIALOG_TAG); 201 } 202 203 private final MainActivity mMainActivity; 204 private final ChannelTuner mChannelTuner; 205 private final TvTransitionManager mTransitionManager; 206 private final ChannelDataManager mChannelDataManager; 207 private final TvInputManagerHelper mInputManager; 208 private final Menu mMenu; 209 private final TunableTvView mTvView; 210 private final SideFragmentManager mSideFragmentManager; 211 private final ProgramGuide mProgramGuide; 212 private final ChannelBannerView mChannelBannerView; 213 private final KeypadChannelSwitchView mKeypadChannelSwitchView; 214 private final SelectInputView mSelectInputView; 215 private final ProgramGuideSearchFragment mSearchFragment; 216 private final Tracker mTracker; 217 private SafeDismissDialogFragment mCurrentDialog; 218 private boolean mSetupFragmentActive; 219 private boolean mNewSourcesFragmentActive; 220 private boolean mChannelBannerHiddenBySideFragment; 221 private final Handler mHandler = new TvOverlayHandler(this); 222 223 private @TvOverlayType int mOpenedOverlays; 224 225 private final List<Runnable> mPendingActions = new ArrayList<>(); 226 private final Queue<PendingDialogAction> mPendingDialogActionQueue = new LinkedList<>(); 227 private final TvOptionsRowAdapter.Factory mTvOptionsRowAdapterFactory; 228 229 private OnBackStackChangedListener mOnBackStackChangedListener; 230 TvOverlayManager( MainActivity mainActivity, ChannelTuner channelTuner, TunableTvView tvView, TvOptionsManager optionsManager, KeypadChannelSwitchView keypadChannelSwitchView, ChannelBannerView channelBannerView, InputBannerView inputBannerView, SelectInputView selectInputView, ViewGroup sceneContainer, ProgramGuideSearchFragment searchFragment, @Provided ChannelDataManager channelDataManager, @Provided TvInputManagerHelper tvInputManager, @Provided ProgramDataManager programDataManager, @Provided TvOptionsRowAdapter.Factory mTvOptionsRowAdapterFactory)231 public TvOverlayManager( 232 MainActivity mainActivity, 233 ChannelTuner channelTuner, 234 TunableTvView tvView, 235 TvOptionsManager optionsManager, 236 KeypadChannelSwitchView keypadChannelSwitchView, 237 ChannelBannerView channelBannerView, 238 InputBannerView inputBannerView, 239 SelectInputView selectInputView, 240 ViewGroup sceneContainer, 241 ProgramGuideSearchFragment searchFragment, 242 @Provided ChannelDataManager channelDataManager, 243 @Provided TvInputManagerHelper tvInputManager, 244 @Provided ProgramDataManager programDataManager, 245 @Provided TvOptionsRowAdapter.Factory mTvOptionsRowAdapterFactory) { 246 mMainActivity = mainActivity; 247 mChannelTuner = channelTuner; 248 this.mTvOptionsRowAdapterFactory = mTvOptionsRowAdapterFactory; 249 TvSingletons singletons = TvSingletons.getSingletons(mainActivity); 250 mChannelDataManager = channelDataManager; 251 mInputManager = tvInputManager; 252 mTvView = tvView; 253 mChannelBannerView = channelBannerView; 254 mKeypadChannelSwitchView = keypadChannelSwitchView; 255 mSelectInputView = selectInputView; 256 mSearchFragment = searchFragment; 257 mTracker = singletons.getTracker(); 258 mTransitionManager = 259 new TvTransitionManager( 260 mainActivity, 261 sceneContainer, 262 channelBannerView, 263 inputBannerView, 264 mKeypadChannelSwitchView, 265 selectInputView); 266 mTransitionManager.setListener( 267 new TvTransitionManager.Listener() { 268 @Override 269 public void onSceneChanged(int fromScene, int toScene) { 270 // Call onOverlayOpened first so that the listener can know that a new scene 271 // will be opened when the onOverlayClosed is called. 272 if (toScene != TvTransitionManager.SCENE_TYPE_EMPTY) { 273 onOverlayOpened(convertSceneToOverlayType(toScene)); 274 } 275 if (fromScene != TvTransitionManager.SCENE_TYPE_EMPTY) { 276 onOverlayClosed(convertSceneToOverlayType(fromScene)); 277 } 278 } 279 }); 280 // Menu 281 MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu); 282 mMenu = 283 new Menu( 284 mainActivity, 285 tvView, 286 optionsManager, 287 menuView, 288 new MenuRowFactory(mainActivity, tvView, this.mTvOptionsRowAdapterFactory), 289 new Menu.OnMenuVisibilityChangeListener() { 290 @Override 291 public void onMenuVisibilityChange(boolean visible) { 292 if (visible) { 293 onOverlayOpened(OVERLAY_TYPE_MENU); 294 } else { 295 onOverlayClosed(OVERLAY_TYPE_MENU); 296 } 297 } 298 }); 299 mMenu.setChannelTuner(mChannelTuner); 300 // Side Fragment 301 mSideFragmentManager = 302 new SideFragmentManager( 303 mainActivity, 304 () -> { 305 onOverlayOpened(OVERLAY_TYPE_SIDE_FRAGMENT); 306 hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS); 307 }, 308 () -> { 309 showChannelBannerIfHiddenBySideFragment(); 310 onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT); 311 }); 312 // Program Guide 313 Runnable preShowRunnable = () -> onOverlayOpened(OVERLAY_TYPE_GUIDE); 314 Runnable postHideRunnable = () -> onOverlayClosed(OVERLAY_TYPE_GUIDE); 315 DvrDataManager dvrDataManager = 316 CommonFeatures.DVR.isEnabled(mainActivity) ? singletons.getDvrDataManager() : null; 317 mProgramGuide = 318 new ProgramGuide( 319 mainActivity, 320 channelTuner, 321 mInputManager, 322 mChannelDataManager, 323 programDataManager, 324 dvrDataManager, 325 singletons.getDvrScheduleManager(), 326 singletons.getTracker(), 327 preShowRunnable, 328 postHideRunnable); 329 mMainActivity.addOnActionClickListener( 330 new OnActionClickListener() { 331 @Override 332 public boolean onActionClick(String category, int id, Bundle params) { 333 switch (category) { 334 case SetupSourcesFragment.ACTION_CATEGORY: 335 switch (id) { 336 case SetupMultiPaneFragment.ACTION_DONE: 337 closeSetupFragment(true); 338 return true; 339 case SetupSourcesFragment.ACTION_ONLINE_STORE: 340 mMainActivity.showMerchantCollection(); 341 return true; 342 case SetupSourcesFragment.ACTION_SETUP_INPUT: 343 { 344 String inputId = 345 params.getString( 346 SetupSourcesFragment 347 .ACTION_PARAM_KEY_INPUT_ID); 348 TvInputInfo input = 349 mInputManager.getTvInputInfo(inputId); 350 mMainActivity.startSetupActivity(input, true); 351 return true; 352 } 353 } 354 break; 355 case NewSourcesFragment.ACTION_CATEOGRY: 356 switch (id) { 357 case NewSourcesFragment.ACTION_SETUP: 358 closeNewSourcesFragment(false); 359 showSetupFragment(); 360 return true; 361 case NewSourcesFragment.ACTION_SKIP: 362 // Don't remove the fragment because new fragment will be 363 // replaced 364 // with this fragment. 365 closeNewSourcesFragment(true); 366 return true; 367 } 368 break; 369 } 370 return false; 371 } 372 }); 373 } 374 375 /** 376 * A method to release all the allocated resources or unregister listeners. This is called from 377 * {@link MainActivity#onDestroy}. 378 */ release()379 public void release() { 380 mMenu.release(); 381 mHandler.removeCallbacksAndMessages(null); 382 if (mKeypadChannelSwitchView != null) { 383 mKeypadChannelSwitchView.setChannels(null); 384 } 385 } 386 387 /** Returns the instance of {@link Menu}. */ getMenu()388 public Menu getMenu() { 389 return mMenu; 390 } 391 392 /** Returns the instance of {@link SideFragmentManager}. */ getSideFragmentManager()393 public SideFragmentManager getSideFragmentManager() { 394 return mSideFragmentManager; 395 } 396 397 /** Returns the currently opened dialog. */ getCurrentDialog()398 public SafeDismissDialogFragment getCurrentDialog() { 399 return mCurrentDialog; 400 } 401 402 /** Checks whether the setup fragment is active or not. */ isSetupFragmentActive()403 public boolean isSetupFragmentActive() { 404 // "getSetupSourcesFragment() != null" doesn't return the correct state. That's because, 405 // when we call showSetupFragment(), we need to put off showing the fragment until the side 406 // fragment is closed. Until then, getSetupSourcesFragment() returns null. So we need 407 // to keep additional variable which indicates if showSetupFragment() is called. 408 return mSetupFragmentActive; 409 } 410 getSetupSourcesFragment()411 private Fragment getSetupSourcesFragment() { 412 return mMainActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG_SETUP_SOURCES); 413 } 414 415 /** Checks whether the new sources fragment is active or not. */ isNewSourcesFragmentActive()416 public boolean isNewSourcesFragmentActive() { 417 // See the comment in "isSetupFragmentActive". 418 return mNewSourcesFragmentActive; 419 } 420 getNewSourcesFragment()421 private Fragment getNewSourcesFragment() { 422 return mMainActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG_NEW_SOURCES); 423 } 424 425 /** Returns the instance of {@link ProgramGuide}. */ getProgramGuide()426 public ProgramGuide getProgramGuide() { 427 return mProgramGuide; 428 } 429 430 /** Shows the main menu. */ showMenu(@enuShowReason int reason)431 public void showMenu(@MenuShowReason int reason) { 432 if (mChannelTuner != null && mChannelTuner.areAllChannelsLoaded()) { 433 mMenu.show(reason); 434 } 435 } 436 437 /** Shows the play controller of the menu if the playback is paused. */ showMenuWithTimeShiftPauseIfNeeded()438 public boolean showMenuWithTimeShiftPauseIfNeeded() { 439 if (mMainActivity.getTimeShiftManager().isPaused()) { 440 showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE); 441 return true; 442 } 443 return false; 444 } 445 446 /** Shows the given dialog. */ showDialogFragment( String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory)447 public void showDialogFragment( 448 String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory) { 449 showDialogFragment(tag, dialog, keepSidePanelHistory, false); 450 } 451 showDialogFragment( String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory, boolean keepProgramGuide)452 public void showDialogFragment( 453 String tag, 454 SafeDismissDialogFragment dialog, 455 boolean keepSidePanelHistory, 456 boolean keepProgramGuide) { 457 int flags = FLAG_HIDE_OVERLAYS_KEEP_DIALOG; 458 if (keepSidePanelHistory) { 459 flags |= FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY; 460 } 461 if (keepProgramGuide) { 462 flags |= FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE; 463 } 464 hideOverlays(flags); 465 // A tag for dialog must be added to AVAILABLE_DIALOG_TAGS to make it launchable from TV. 466 if (!AVAILABLE_DIALOG_TAGS.contains(tag)) { 467 return; 468 } 469 470 // Do not open two dialogs at the same time. 471 if (mCurrentDialog != null) { 472 mPendingDialogActionQueue.offer( 473 new PendingDialogAction(tag, dialog, keepSidePanelHistory, keepProgramGuide)); 474 return; 475 } 476 477 mCurrentDialog = dialog; 478 dialog.show(mMainActivity.getFragmentManager(), tag); 479 480 // Calling this from SafeDismissDialogFragment.onCreated() might be late 481 // because it takes time for onCreated to be called 482 // and next key events can be handled by MainActivity, not Dialog. 483 onOverlayOpened(OVERLAY_TYPE_DIALOG); 484 } 485 486 /** 487 * Should be called by {@link MainActivity} when the currently browsable channels are updated. 488 */ onBrowsableChannelsUpdated()489 public void onBrowsableChannelsUpdated() { 490 mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList()); 491 } 492 runAfterSideFragmentsAreClosed(final Runnable runnable)493 private void runAfterSideFragmentsAreClosed(final Runnable runnable) { 494 if (mSideFragmentManager.isSidePanelVisible()) { 495 // When the side panel is closing, it closes all the fragments, so the new fragment 496 // should be opened after the side fragment becomes invisible. 497 final FragmentManager manager = mMainActivity.getFragmentManager(); 498 mOnBackStackChangedListener = 499 new OnBackStackChangedListener() { 500 @Override 501 public void onBackStackChanged() { 502 if (manager.getBackStackEntryCount() == 0) { 503 manager.removeOnBackStackChangedListener(this); 504 mOnBackStackChangedListener = null; 505 runnable.run(); 506 } 507 } 508 }; 509 manager.addOnBackStackChangedListener(mOnBackStackChangedListener); 510 } else { 511 runnable.run(); 512 } 513 } 514 showFragment(final Fragment fragment, final String tag)515 private void showFragment(final Fragment fragment, final String tag) { 516 hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); 517 onOverlayOpened(OVERLAY_TYPE_FRAGMENT); 518 runAfterSideFragmentsAreClosed( 519 () -> { 520 if (DEBUG) Log.d(TAG, "showFragment(" + fragment + ")"); 521 mMainActivity 522 .getFragmentManager() 523 .beginTransaction() 524 .replace(R.id.fragment_container, fragment, tag) 525 .commit(); 526 }); 527 } 528 closeFragment(String fragmentTagToRemove)529 private void closeFragment(String fragmentTagToRemove) { 530 if (DEBUG) Log.d(TAG, "closeFragment(" + fragmentTagToRemove + ")"); 531 onOverlayClosed(OVERLAY_TYPE_FRAGMENT); 532 if (fragmentTagToRemove != null) { 533 Fragment fragmentToRemove = 534 mMainActivity.getFragmentManager().findFragmentByTag(fragmentTagToRemove); 535 if (fragmentToRemove == null) { 536 // If the fragment has not been added to the fragment manager yet, just remove the 537 // listener not to add the fragment. This is needed because the side fragment is 538 // closed asynchronously. 539 mMainActivity 540 .getFragmentManager() 541 .removeOnBackStackChangedListener(mOnBackStackChangedListener); 542 mOnBackStackChangedListener = null; 543 } else { 544 mMainActivity 545 .getFragmentManager() 546 .beginTransaction() 547 .remove(fragmentToRemove) 548 .commit(); 549 } 550 } 551 } 552 553 /** Shows setup dialog. */ showSetupFragment()554 public void showSetupFragment() { 555 if (DEBUG) Log.d(TAG, "showSetupFragment"); 556 mSetupFragmentActive = true; 557 SetupSourcesFragment setupFragment = new SetupSourcesFragment(); 558 setupFragment.enableFragmentTransition( 559 SetupFragment.FRAGMENT_ENTER_TRANSITION 560 | SetupFragment.FRAGMENT_EXIT_TRANSITION 561 | SetupFragment.FRAGMENT_RETURN_TRANSITION 562 | SetupFragment.FRAGMENT_REENTER_TRANSITION); 563 setupFragment.setFragmentTransition(SetupFragment.FRAGMENT_EXIT_TRANSITION, Gravity.END); 564 showFragment(setupFragment, FRAGMENT_TAG_SETUP_SOURCES); 565 } 566 567 // Set removeFragment to false only when the new fragment is going to be shown. closeSetupFragment(boolean removeFragment)568 private void closeSetupFragment(boolean removeFragment) { 569 if (DEBUG) Log.d(TAG, "closeSetupFragment"); 570 if (!mSetupFragmentActive) { 571 return; 572 } 573 mSetupFragmentActive = false; 574 closeFragment(removeFragment ? FRAGMENT_TAG_SETUP_SOURCES : null); 575 if (mChannelDataManager.getChannelCount() == 0) { 576 if (DEBUG) Log.d(TAG, "Finishing MainActivity because there are no channels."); 577 mMainActivity.finish(); 578 } 579 } 580 581 /** Shows new sources dialog. */ showNewSourcesFragment()582 public void showNewSourcesFragment() { 583 if (DEBUG) Log.d(TAG, "showNewSourcesFragment"); 584 mNewSourcesFragmentActive = true; 585 showFragment(new NewSourcesFragment(), FRAGMENT_TAG_NEW_SOURCES); 586 } 587 588 // Set removeFragment to false only when the new fragment is going to be shown. closeNewSourcesFragment(boolean removeFragment)589 private void closeNewSourcesFragment(boolean removeFragment) { 590 if (DEBUG) Log.d(TAG, "closeNewSourcesFragment"); 591 if (!mNewSourcesFragmentActive) { 592 return; 593 } 594 mNewSourcesFragmentActive = false; 595 closeFragment(removeFragment ? FRAGMENT_TAG_NEW_SOURCES : null); 596 } 597 598 /** Shows DVR manager. */ showDvrManager()599 public void showDvrManager() { 600 Intent intent = new Intent(mMainActivity, DvrBrowseActivity.class); 601 mMainActivity.startActivity(intent); 602 } 603 604 /** Shows intro dialog. */ showIntroDialog()605 public void showIntroDialog() { 606 if (DEBUG) Log.d(TAG, "showIntroDialog"); 607 showDialogFragment( 608 FullscreenDialogFragment.DIALOG_TAG, 609 FullscreenDialogFragment.newInstance(R.layout.intro_dialog, INTRO_TRACKER_LABEL), 610 false); 611 } 612 613 /** Shows recently watched dialog. */ showRecentlyWatchedDialog()614 public void showRecentlyWatchedDialog() { 615 showDialogFragment( 616 RecentlyWatchedDialogFragment.DIALOG_TAG, 617 new RecentlyWatchedDialogFragment(), 618 false); 619 } 620 621 /** Shows DVR history dialog. */ showDvrHistoryDialog()622 public void showDvrHistoryDialog() { 623 showDialogFragment( 624 DvrHistoryDialogFragment.DIALOG_TAG, new DvrHistoryDialogFragment(), false); 625 } 626 627 /** Shows banner view. */ showBanner()628 public void showBanner() { 629 mTransitionManager.goToChannelBannerScene(); 630 } 631 632 /** 633 * Pops up the KeypadChannelSwitchView with the given key input event. 634 * 635 * @param keyCode A key code of the key event. 636 */ showKeypadChannelSwitch(int keyCode)637 public void showKeypadChannelSwitch(int keyCode) { 638 if (mChannelTuner.areAllChannelsLoaded()) { 639 hideOverlays( 640 TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE 641 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS 642 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG 643 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); 644 mTransitionManager.goToKeypadChannelSwitchScene(); 645 mKeypadChannelSwitchView.onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0); 646 } 647 } 648 649 /** Shows select input view. */ showSelectInputView()650 public void showSelectInputView() { 651 hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE); 652 mTransitionManager.goToSelectInputScene(); 653 } 654 655 /** Initializes animators if animators are not initialized yet. */ initAnimatorIfNeeded()656 public void initAnimatorIfNeeded() { 657 mTransitionManager.initIfNeeded(); 658 } 659 660 /** It is called when a SafeDismissDialogFragment is destroyed. */ onDialogDestroyed()661 public void onDialogDestroyed() { 662 mCurrentDialog = null; 663 PendingDialogAction action = mPendingDialogActionQueue.poll(); 664 if (action == null) { 665 onOverlayClosed(OVERLAY_TYPE_DIALOG); 666 } else { 667 action.run(); 668 } 669 } 670 671 /** Shows the program guide. */ showProgramGuide()672 public void showProgramGuide() { 673 mProgramGuide.show( 674 () -> hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE)); 675 } 676 677 /** 678 * Shows/hides the program guide according to it's hidden or shown now. 679 * 680 * @return {@code true} if program guide is going to be shown, otherwise {@code false}. 681 */ toggleProgramGuide()682 public boolean toggleProgramGuide() { 683 if (mProgramGuide.isActive()) { 684 mProgramGuide.onBackPressed(); 685 return false; 686 } else { 687 showProgramGuide(); 688 return true; 689 } 690 } 691 692 /** Sets blocking content rating of the currently playing TV channel. */ setBlockingContentRating(TvContentRating rating)693 public void setBlockingContentRating(TvContentRating rating) { 694 if (!mMainActivity.isChannelChangeKeyDownReceived()) { 695 mChannelBannerView.setBlockingContentRating(rating); 696 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); 697 } 698 } 699 isOverlayOpened()700 public boolean isOverlayOpened() { 701 return mOpenedOverlays != OVERLAY_TYPE_NONE; 702 } 703 704 /** Hides all the opened overlays according to the flags. */ 705 // TODO: Add test for this method. hideOverlays(@ideOverlayFlag int flags)706 public void hideOverlays(@HideOverlayFlag int flags) { 707 if (mMainActivity.needToKeepSetupScreenWhenHidingOverlay()) { 708 flags |= FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT; 709 } 710 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_DIALOG) != 0) { 711 // Keeps the dialog. 712 } else { 713 if (mCurrentDialog != null) { 714 if (mCurrentDialog instanceof PinDialogFragment) { 715 // We don't want any OnPinCheckedListener is triggered to prevent any possible 716 // side effects. Dismisses the dialog silently. 717 ((PinDialogFragment) mCurrentDialog).dismissSilently(); 718 } else { 719 mCurrentDialog.dismiss(); 720 } 721 } 722 mPendingDialogActionQueue.clear(); 723 mCurrentDialog = null; 724 } 725 boolean withAnimation = (flags & FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION) == 0; 726 727 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT) == 0) { 728 Fragment setupSourcesFragment = getSetupSourcesFragment(); 729 Fragment newSourcesFragment = getNewSourcesFragment(); 730 if (mSetupFragmentActive) { 731 if (!withAnimation && setupSourcesFragment != null) { 732 setupSourcesFragment.setReturnTransition(null); 733 setupSourcesFragment.setExitTransition(null); 734 } 735 closeSetupFragment(true); 736 } 737 if (mNewSourcesFragmentActive) { 738 if (!withAnimation && newSourcesFragment != null) { 739 newSourcesFragment.setReturnTransition(null); 740 newSourcesFragment.setExitTransition(null); 741 } 742 closeNewSourcesFragment(true); 743 } 744 } 745 746 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_MENU) != 0) { 747 // Keeps the menu. 748 } else { 749 mMenu.hide(withAnimation); 750 } 751 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SCENE) != 0) { 752 // Keeps the current scene. 753 } else { 754 mTransitionManager.goToEmptyScene(withAnimation); 755 } 756 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS) != 0) { 757 // Keeps side panels. 758 } else if (mSideFragmentManager.isActive()) { 759 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY) != 0) { 760 mSideFragmentManager.hideSidePanel(withAnimation); 761 } else { 762 mSideFragmentManager.hideAll(withAnimation); 763 } 764 } 765 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE) != 0) { 766 // Keep the program guide. 767 } else { 768 mProgramGuide.hide(); 769 } 770 } 771 772 @Override onAccessibilityStateChanged(boolean enabled)773 public void onAccessibilityStateChanged(boolean enabled) { 774 // Propagate this to all elements that need it 775 mChannelBannerView.onAccessibilityStateChanged(enabled); 776 mProgramGuide.onAccessibilityStateChanged(enabled); 777 mSideFragmentManager.onAccessibilityStateChanged(enabled); 778 } 779 780 /** 781 * Returns true, if a main view needs to hide informational text. Specifically, when overlay UIs 782 * except banner is shown, the informational text needs to be hidden for clean UI. 783 */ needHideTextOnMainView()784 public boolean needHideTextOnMainView() { 785 return mSideFragmentManager.isActive() 786 || getMenu().isActive() 787 || mTransitionManager.isKeypadChannelSwitchActive() 788 || mTransitionManager.isSelectInputActive() 789 || mSetupFragmentActive 790 || mNewSourcesFragmentActive; 791 } 792 793 /** Updates and shows channel banner if it's needed. */ updateChannelBannerAndShowIfNeeded(@hannelBannerUpdateReason int reason)794 public void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) { 795 if (DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")"); 796 if (mMainActivity.isChannelChangeKeyDownReceived() 797 && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE 798 && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) { 799 // Tuning is still ongoing, no need to update banner for other reasons 800 return; 801 } 802 if (!mChannelTuner.isCurrentChannelPassthrough()) { 803 int lockType = ChannelBannerView.LOCK_NONE; 804 if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) { 805 if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled() 806 && mMainActivity.getCurrentChannel().isLocked()) { 807 lockType = ChannelBannerView.LOCK_CHANNEL_INFO; 808 } else { 809 // Do not show detailed program information while fast-tuning. 810 lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; 811 } 812 } else if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE) { 813 if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled()) { 814 if (mMainActivity.getCurrentChannel().isLocked()) { 815 lockType = ChannelBannerView.LOCK_CHANNEL_INFO; 816 } else { 817 // If parental control is turned on, 818 // assumes that program is locked by default and waits for onContentAllowed. 819 lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; 820 } 821 } 822 } else if (mTvView.isScreenBlocked()) { 823 lockType = ChannelBannerView.LOCK_CHANNEL_INFO; 824 } else if (mTvView.isContentBlocked() 825 || (mMainActivity.getParentalControlSettings().isParentalControlsEnabled() 826 && !mTvView.isVideoOrAudioAvailable())) { 827 // If the parental control is enabled, do not show the program detail until the 828 // video becomes available. 829 lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; 830 } 831 // If lock type is not changed, we don't need to update channel banner by parental 832 // control. 833 int previousLockType = mChannelBannerView.setLockType(lockType); 834 if (previousLockType == lockType 835 && reason == UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK) { 836 return; 837 } else if (reason == UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO) { 838 mChannelBannerView.updateStreamInfo(mTvView); 839 // If parental control is enabled, we shows program description when the video is 840 // available, instead of tuning. Therefore we need to check it here if the program 841 // description is previously hidden by parental control. 842 if (previousLockType == ChannelBannerView.LOCK_PROGRAM_DETAIL 843 && lockType != ChannelBannerView.LOCK_PROGRAM_DETAIL) { 844 mChannelBannerView.updateViews(false); 845 } 846 } else if (CommonFeatures.TUNER_SIGNAL_STRENGTH.isEnabled(mMainActivity) 847 && reason == UPDATE_CHANNEL_BANNER_REASON_UPDATE_SIGNAL_STRENGTH) { 848 mChannelBannerView.updateChannelSignalStrengthView( 849 mTvView.getChannelSignalStrength()); 850 } else { 851 mChannelBannerView.updateViews( 852 reason == UPDATE_CHANNEL_BANNER_REASON_TUNE 853 || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); 854 } 855 } 856 boolean needToShowBanner = 857 (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW 858 || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE 859 || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); 860 if (needToShowBanner 861 && !mMainActivity.willShowOverlayUiWhenResume() 862 && getCurrentDialog() == null 863 && !isSetupFragmentActive() 864 && !isNewSourcesFragmentActive()) { 865 if (mChannelTuner.getCurrentChannel() == null) { 866 mChannelBannerHiddenBySideFragment = false; 867 } else if (getSideFragmentManager().isActive()) { 868 mChannelBannerHiddenBySideFragment = true; 869 } else { 870 mChannelBannerHiddenBySideFragment = false; 871 showBanner(); 872 } 873 } 874 } 875 876 @TvOverlayType convertSceneToOverlayType(@ceneType int sceneType)877 private int convertSceneToOverlayType(@SceneType int sceneType) { 878 switch (sceneType) { 879 case TvTransitionManager.SCENE_TYPE_CHANNEL_BANNER: 880 return OVERLAY_TYPE_SCENE_CHANNEL_BANNER; 881 case TvTransitionManager.SCENE_TYPE_INPUT_BANNER: 882 return OVERLAY_TYPE_SCENE_INPUT_BANNER; 883 case TvTransitionManager.SCENE_TYPE_KEYPAD_CHANNEL_SWITCH: 884 return OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH; 885 case TvTransitionManager.SCENE_TYPE_SELECT_INPUT: 886 return OVERLAY_TYPE_SCENE_SELECT_INPUT; 887 case TvTransitionManager.SCENE_TYPE_EMPTY: 888 default: 889 return OVERLAY_TYPE_NONE; 890 } 891 } 892 onOverlayOpened(@vOverlayType int overlayType)893 private void onOverlayOpened(@TvOverlayType int overlayType) { 894 if (DEBUG) Log.d(TAG, "Overlay opened: " + toBinaryString(overlayType)); 895 mOpenedOverlays |= overlayType; 896 if (DEBUG) Log.d(TAG, "Opened overlays: " + toBinaryString(mOpenedOverlays)); 897 mHandler.removeMessages(MSG_OVERLAY_CLOSED); 898 mMainActivity.updateKeyInputFocus(); 899 } 900 onOverlayClosed(@vOverlayType int overlayType)901 private void onOverlayClosed(@TvOverlayType int overlayType) { 902 if (DEBUG) Log.d(TAG, "Overlay closed: " + toBinaryString(overlayType)); 903 mOpenedOverlays &= ~overlayType; 904 if (DEBUG) Log.d(TAG, "Opened overlays: " + toBinaryString(mOpenedOverlays)); 905 mHandler.removeMessages(MSG_OVERLAY_CLOSED); 906 mMainActivity.updateKeyInputFocus(); 907 // Show the main menu again if there are no pop-ups or banners only. 908 // The main menu should not be shown when the activity is in paused state. 909 boolean menuAboutToShow = false; 910 if (canExecuteCloseAction()) { 911 menuAboutToShow = mMainActivity.getTimeShiftManager().isPaused(); 912 mHandler.sendEmptyMessage(MSG_OVERLAY_CLOSED); 913 } 914 // Don't set screen name to main if the overlay closing is a banner 915 // or if a non banner overlay is still open 916 // or if we just opened the menu 917 if (overlayType != OVERLAY_TYPE_SCENE_CHANNEL_BANNER 918 && overlayType != OVERLAY_TYPE_SCENE_INPUT_BANNER 919 && isOnlyBannerOrNoneOpened() 920 && !menuAboutToShow) { 921 mTracker.sendScreenView(MainActivity.SCREEN_NAME); 922 } 923 } 924 925 /** 926 * Shows the channel banner if it was hidden from the side fragment. 927 * 928 * <p>When the side fragment is visible, showing the channel banner should be put off until the 929 * side fragment is closed even though the channel changes. 930 */ showChannelBannerIfHiddenBySideFragment()931 private void showChannelBannerIfHiddenBySideFragment() { 932 if (mChannelBannerHiddenBySideFragment) { 933 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); 934 } 935 } 936 toBinaryString(int value)937 private String toBinaryString(int value) { 938 return String.format("0b%" + NUM_OVERLAY_TYPES + "s", Integer.toBinaryString(value)) 939 .replace(' ', '0'); 940 } 941 canExecuteCloseAction()942 private boolean canExecuteCloseAction() { 943 return mMainActivity.isActivityResumed() && isOnlyBannerOrNoneOpened(); 944 } 945 isOnlyBannerOrNoneOpened()946 private boolean isOnlyBannerOrNoneOpened() { 947 return (mOpenedOverlays 948 & ~OVERLAY_TYPE_SCENE_CHANNEL_BANNER 949 & ~OVERLAY_TYPE_SCENE_INPUT_BANNER) 950 == 0; 951 } 952 953 /** Runs a given {@code action} after all the overlays are closed. */ runAfterOverlaysAreClosed(Runnable action)954 public void runAfterOverlaysAreClosed(Runnable action) { 955 if (canExecuteCloseAction()) { 956 action.run(); 957 } else { 958 mPendingActions.add(action); 959 } 960 } 961 962 /** Handles the onUserInteraction event of the {@link MainActivity}. */ onUserInteraction()963 public void onUserInteraction() { 964 if (mSideFragmentManager.isActive()) { 965 mSideFragmentManager.scheduleHideAll(); 966 } else if (mMenu.isActive()) { 967 mMenu.scheduleHide(); 968 } else if (mProgramGuide.isActive()) { 969 mProgramGuide.scheduleHide(); 970 } 971 } 972 973 /** Handles the onKeyDown event of the {@link MainActivity}. */ 974 @KeyHandlerResultType onKeyDown(int keyCode, KeyEvent event)975 public int onKeyDown(int keyCode, KeyEvent event) { 976 if (mCurrentDialog != null) { 977 // Consumes the keys while a Dialog is creating. 978 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 979 } 980 // Handle media key here because it is related to the menu. 981 if (isMediaStartKey(keyCode)) { 982 // Consumes the keys which may trigger system's default music player. 983 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 984 } 985 if (mMenu.isActive() 986 || mSideFragmentManager.isActive() 987 || mProgramGuide.isActive() 988 || mSetupFragmentActive 989 || mNewSourcesFragmentActive) { 990 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 991 } 992 if (mTransitionManager.isKeypadChannelSwitchActive()) { 993 return mKeypadChannelSwitchView.onKeyDown(keyCode, event) 994 ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED 995 : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; 996 } 997 if (mTransitionManager.isSelectInputActive()) { 998 return mSelectInputView.onKeyDown(keyCode, event) 999 ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED 1000 : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; 1001 } 1002 return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH; 1003 } 1004 1005 /** Handles the onKeyUp event of the {@link MainActivity}. */ 1006 @KeyHandlerResultType onKeyUp(int keyCode, KeyEvent event)1007 public int onKeyUp(int keyCode, KeyEvent event) { 1008 // Handle media key here because it is related to the menu. 1009 if (isMediaStartKey(keyCode)) { 1010 // The media key should not be passed up to the system in any cases. 1011 if (mCurrentDialog != null 1012 || mProgramGuide.isActive() 1013 || mSideFragmentManager.isActive() 1014 || mSearchFragment.isVisible() 1015 || mTransitionManager.isKeypadChannelSwitchActive() 1016 || mTransitionManager.isSelectInputActive() 1017 || mSetupFragmentActive 1018 || mNewSourcesFragmentActive) { 1019 // Do not handle media key when any pop-ups which can handle keys are active. 1020 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1021 } 1022 if (mTvView.isScreenBlocked()) { 1023 // Do not handle media key when screen is blocked. 1024 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1025 } 1026 TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager(); 1027 if (!timeShiftManager.isAvailable()) { 1028 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1029 } 1030 switch (keyCode) { 1031 case KeyEvent.KEYCODE_MEDIA_PLAY: 1032 timeShiftManager.play(); 1033 showMenu(Menu.REASON_PLAY_CONTROLS_PLAY); 1034 break; 1035 case KeyEvent.KEYCODE_MEDIA_STOP: 1036 case KeyEvent.KEYCODE_MEDIA_PAUSE: 1037 timeShiftManager.pause(); 1038 showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE); 1039 break; 1040 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 1041 timeShiftManager.togglePlayPause(); 1042 showMenu(Menu.REASON_PLAY_CONTROLS_PLAY_PAUSE); 1043 break; 1044 case KeyEvent.KEYCODE_MEDIA_REWIND: 1045 timeShiftManager.rewind(); 1046 showMenu(Menu.REASON_PLAY_CONTROLS_REWIND); 1047 break; 1048 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 1049 timeShiftManager.fastForward(); 1050 showMenu(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD); 1051 break; 1052 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 1053 case KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD: 1054 timeShiftManager.jumpToPrevious(); 1055 showMenu(Menu.REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS); 1056 break; 1057 case KeyEvent.KEYCODE_MEDIA_NEXT: 1058 case KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD: 1059 timeShiftManager.jumpToNext(); 1060 showMenu(Menu.REASON_PLAY_CONTROLS_JUMP_TO_NEXT); 1061 break; 1062 default: 1063 // Does nothing. 1064 break; 1065 } 1066 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1067 } 1068 if (keyCode == KeyEvent.KEYCODE_I || keyCode == KeyEvent.KEYCODE_TV_INPUT) { 1069 if (mTransitionManager.isSelectInputActive()) { 1070 mSelectInputView.onKeyUp(keyCode, event); 1071 } else { 1072 showSelectInputView(); 1073 } 1074 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1075 } 1076 if (mCurrentDialog != null) { 1077 // Consumes the keys while a Dialog is showing. 1078 // This can be happen while a Dialog isn't created yet. 1079 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1080 } 1081 if (mProgramGuide.isActive()) { 1082 if (keyCode == KeyEvent.KEYCODE_BACK) { 1083 mProgramGuide.onBackPressed(); 1084 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1085 } 1086 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 1087 } 1088 if (mSideFragmentManager.isActive()) { 1089 if (keyCode == KeyEvent.KEYCODE_BACK 1090 || mSideFragmentManager.isHideKeyForCurrentPanel(keyCode)) { 1091 mSideFragmentManager.popSideFragment(); 1092 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1093 } 1094 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 1095 } 1096 if (mMenu.isActive() || mTransitionManager.isSceneActive()) { 1097 if (keyCode == KeyEvent.KEYCODE_BACK) { 1098 TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager(); 1099 if (timeShiftManager.isPaused()) { 1100 timeShiftManager.play(); 1101 } 1102 hideOverlays( 1103 TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS 1104 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG 1105 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); 1106 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1107 } 1108 if (mMenu.isActive()) { 1109 if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) { 1110 showKeypadChannelSwitch(keyCode); 1111 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1112 } 1113 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 1114 } 1115 } 1116 if (mTransitionManager.isKeypadChannelSwitchActive()) { 1117 if (keyCode == KeyEvent.KEYCODE_BACK) { 1118 mTransitionManager.goToEmptyScene(true); 1119 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1120 } 1121 return mKeypadChannelSwitchView.onKeyUp(keyCode, event) 1122 ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED 1123 : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; 1124 } 1125 if (mTransitionManager.isSelectInputActive()) { 1126 if (keyCode == KeyEvent.KEYCODE_BACK) { 1127 mTransitionManager.goToEmptyScene(true); 1128 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1129 } 1130 return mSelectInputView.onKeyUp(keyCode, event) 1131 ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED 1132 : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; 1133 } 1134 if (mSetupFragmentActive) { 1135 if (keyCode == KeyEvent.KEYCODE_BACK) { 1136 closeSetupFragment(true); 1137 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1138 } 1139 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 1140 } 1141 if (mNewSourcesFragmentActive) { 1142 if (keyCode == KeyEvent.KEYCODE_BACK) { 1143 closeNewSourcesFragment(true); 1144 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 1145 } 1146 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 1147 } 1148 return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH; 1149 } 1150 1151 /** Checks whether the given {@code keyCode} can start the system's music app or not. */ isMediaStartKey(int keyCode)1152 private static boolean isMediaStartKey(int keyCode) { 1153 switch (keyCode) { 1154 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 1155 case KeyEvent.KEYCODE_MEDIA_PLAY: 1156 case KeyEvent.KEYCODE_MEDIA_PAUSE: 1157 case KeyEvent.KEYCODE_MEDIA_NEXT: 1158 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 1159 case KeyEvent.KEYCODE_MEDIA_REWIND: 1160 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 1161 case KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD: 1162 case KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD: 1163 case KeyEvent.KEYCODE_MEDIA_STOP: 1164 return true; 1165 } 1166 return false; 1167 } 1168 1169 private static class TvOverlayHandler extends WeakHandler<TvOverlayManager> { TvOverlayHandler(TvOverlayManager ref)1170 TvOverlayHandler(TvOverlayManager ref) { 1171 super(ref); 1172 } 1173 1174 @Override handleMessage(Message msg, @NonNull TvOverlayManager tvOverlayManager)1175 public void handleMessage(Message msg, @NonNull TvOverlayManager tvOverlayManager) { 1176 switch (msg.what) { 1177 case MSG_OVERLAY_CLOSED: 1178 if (!tvOverlayManager.canExecuteCloseAction()) { 1179 return; 1180 } 1181 if (tvOverlayManager.showMenuWithTimeShiftPauseIfNeeded()) { 1182 return; 1183 } 1184 if (!tvOverlayManager.mPendingActions.isEmpty()) { 1185 Runnable action = tvOverlayManager.mPendingActions.get(0); 1186 tvOverlayManager.mPendingActions.remove(action); 1187 action.run(); 1188 } 1189 break; 1190 } 1191 } 1192 } 1193 1194 private class PendingDialogAction { 1195 private final String mTag; 1196 private final SafeDismissDialogFragment mDialog; 1197 private final boolean mKeepSidePanelHistory; 1198 private final boolean mKeepProgramGuide; 1199 PendingDialogAction( String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory, boolean keepProgramGuide)1200 PendingDialogAction( 1201 String tag, 1202 SafeDismissDialogFragment dialog, 1203 boolean keepSidePanelHistory, 1204 boolean keepProgramGuide) { 1205 mTag = tag; 1206 mDialog = dialog; 1207 mKeepSidePanelHistory = keepSidePanelHistory; 1208 mKeepProgramGuide = keepProgramGuide; 1209 } 1210 run()1211 void run() { 1212 showDialogFragment(mTag, mDialog, mKeepSidePanelHistory, mKeepProgramGuide); 1213 } 1214 } 1215 } 1216