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