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