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 TvOverlayManager(MainActivity mainActivity, ChannelTuner channelTuner, TunableTvView tvView, TvOptionsManager optionsManager, KeypadChannelSwitchView keypadChannelSwitchView, ChannelBannerView channelBannerView, InputBannerView inputBannerView, SelectInputView selectInputView, ViewGroup sceneContainer, ProgramGuideSearchFragment searchFragment)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 */ release()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 */ getMenu()372 public Menu getMenu() { 373 return mMenu; 374 } 375 376 /** 377 * Returns the instance of {@link SideFragmentManager}. 378 */ getSideFragmentManager()379 public SideFragmentManager getSideFragmentManager() { 380 return mSideFragmentManager; 381 } 382 383 /** 384 * Returns the currently opened dialog. 385 */ getCurrentDialog()386 public SafeDismissDialogFragment getCurrentDialog() { 387 return mCurrentDialog; 388 } 389 390 /** 391 * Checks whether the setup fragment is active or not. 392 */ isSetupFragmentActive()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 getSetupSourcesFragment()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 */ isNewSourcesFragmentActive()408 public boolean isNewSourcesFragmentActive() { 409 // See the comment in "isSetupFragmentActive". 410 return mNewSourcesFragmentActive; 411 } 412 getNewSourcesFragment()413 private Fragment getNewSourcesFragment() { 414 return mMainActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG_NEW_SOURCES); 415 } 416 417 /** 418 * Returns the instance of {@link ProgramGuide}. 419 */ getProgramGuide()420 public ProgramGuide getProgramGuide() { 421 return mProgramGuide; 422 } 423 424 /** 425 * Shows the main menu. 426 */ showMenu(@enuShowReason int reason)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 */ showMenuWithTimeShiftPauseIfNeeded()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 */ showDialogFragment(String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory)447 public void showDialogFragment(String tag, SafeDismissDialogFragment dialog, 448 boolean keepSidePanelHistory) { 449 showDialogFragment(tag, dialog, keepSidePanelHistory, false); 450 } 451 showDialogFragment(String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory, boolean keepProgramGuide)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 */ onBrowsableChannelsUpdated()486 public void onBrowsableChannelsUpdated() { 487 mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList()); 488 } 489 runAfterSideFragmentsAreClosed(final Runnable runnable)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 showFragment(final Fragment fragment, final String tag)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 closeFragment(String fragmentTagToRemove)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 */ showSetupFragment()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. closeSetupFragment(boolean removeFragment)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 */ showNewSourcesFragment()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. closeNewSourcesFragment(boolean removeFragment)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 */ showDvrManager()594 public void showDvrManager() { 595 Intent intent = new Intent(mMainActivity, DvrBrowseActivity.class); 596 mMainActivity.startActivity(intent); 597 } 598 599 /** 600 * Shows intro dialog. 601 */ showIntroDialog()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 */ showRecentlyWatchedDialog()612 public void showRecentlyWatchedDialog() { 613 showDialogFragment(RecentlyWatchedDialogFragment.DIALOG_TAG, 614 new RecentlyWatchedDialogFragment(), false); 615 } 616 617 /** 618 * Shows DVR history dialog. 619 */ showDvrHistoryDialog()620 public void showDvrHistoryDialog() { 621 showDialogFragment(DvrHistoryDialogFragment.DIALOG_TAG, 622 new DvrHistoryDialogFragment(), false); 623 } 624 625 /** 626 * Shows banner view. 627 */ 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(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 */ showSelectInputView()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 */ initAnimatorIfNeeded()659 public void initAnimatorIfNeeded() { 660 mTransitionManager.initIfNeeded(); 661 } 662 663 /** 664 * It is called when a SafeDismissDialogFragment is destroyed. 665 */ onDialogDestroyed()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 */ showProgramGuide()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 */ toggleProgramGuide()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 */ setBlockingContentRating(TvContentRating rating)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. hideOverlays(@ideOverlayFlag int flags)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 */ needHideTextOnMainView()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 */ updateChannelBannerAndShowIfNeeded(@hannelBannerUpdateReason int reason)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 convertSceneToOverlayType(@ceneType int sceneType)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 onOverlayOpened(@vOverlayType int overlayType)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 onOverlayClosed(@vOverlayType int overlayType)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 */ showChannelBannerIfHiddenBySideFragment()927 private void showChannelBannerIfHiddenBySideFragment() { 928 if (mChannelBannerHiddenBySideFragment) { 929 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); 930 } 931 } 932 toBinaryString(int value)933 private String toBinaryString(int value) { 934 return String.format("0b%" + NUM_OVERLAY_TYPES + "s", Integer.toBinaryString(value)) 935 .replace(' ', '0'); 936 } 937 canExecuteCloseAction()938 private boolean canExecuteCloseAction() { 939 return mMainActivity.isActivityResumed() && isOnlyBannerOrNoneOpened(); 940 } 941 isOnlyBannerOrNoneOpened()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 */ runAfterOverlaysAreClosed(Runnable action)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 */ onUserInteraction()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 */ onKeyDown(int keyCode, KeyEvent event)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 */ onKeyUp(int keyCode, KeyEvent event)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 */ isMediaStartKey(int keyCode)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> { TvOverlayHandler(TvOverlayManager ref)1163 TvOverlayHandler(TvOverlayManager ref) { 1164 super(ref); 1165 } 1166 1167 @Override handleMessage(Message msg, @NonNull TvOverlayManager tvOverlayManager)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 PendingDialogAction(String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory, boolean keepProgramGuide)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 run()1201 void run() { 1202 showDialogFragment(mTag, mDialog, mKeepSidePanelHistory, mKeepProgramGuide); 1203 } 1204 } 1205 } 1206