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