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