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