TvOverlayManager.java revision 3a72b93e554bd22a5c64e71a6956d9604ce05108
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 17package com.android.tv.ui; 18 19import android.app.Fragment; 20import android.app.FragmentManager; 21import android.app.FragmentManager.OnBackStackChangedListener; 22import android.content.Intent; 23import android.media.tv.TvInputInfo; 24import android.os.Build; 25import android.os.Bundle; 26import android.os.Handler; 27import android.os.Message; 28import android.support.annotation.IntDef; 29import android.support.annotation.NonNull; 30import android.support.annotation.Nullable; 31import android.support.annotation.UiThread; 32import android.util.Log; 33import android.view.Gravity; 34import android.view.KeyEvent; 35import android.view.LayoutInflater; 36import android.view.View; 37import android.view.ViewGroup; 38import android.widget.Space; 39 40import com.android.tv.ApplicationSingletons; 41import com.android.tv.ChannelTuner; 42import com.android.tv.MainActivity; 43import com.android.tv.MainActivity.KeyHandlerResultType; 44import com.android.tv.R; 45import com.android.tv.TimeShiftManager; 46import com.android.tv.TvApplication; 47import com.android.tv.analytics.Tracker; 48import com.android.tv.common.WeakHandler; 49import com.android.tv.common.ui.setup.OnActionClickListener; 50import com.android.tv.common.ui.setup.SetupFragment; 51import com.android.tv.common.ui.setup.SetupMultiPaneFragment; 52import com.android.tv.data.ChannelDataManager; 53import com.android.tv.dialog.FullscreenDialogFragment; 54import com.android.tv.dialog.PinDialogFragment; 55import com.android.tv.dialog.RecentlyWatchedDialogFragment; 56import com.android.tv.dialog.SafeDismissDialogFragment; 57import com.android.tv.dvr.ui.DvrActivity; 58import com.android.tv.guide.ProgramGuide; 59import com.android.tv.menu.Menu; 60import com.android.tv.menu.Menu.MenuShowReason; 61import com.android.tv.menu.MenuRowFactory; 62import com.android.tv.menu.MenuView; 63import com.android.tv.onboarding.NewSourcesFragment; 64import com.android.tv.onboarding.SetupSourcesFragment; 65import com.android.tv.onboarding.SetupSourcesFragment.InputSetupRunnable; 66import com.android.tv.search.ProgramGuideSearchFragment; 67import com.android.tv.ui.TvTransitionManager.SceneType; 68import com.android.tv.ui.sidepanel.SettingsFragment; 69import com.android.tv.ui.sidepanel.SideFragmentManager; 70import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment; 71 72import java.lang.annotation.Retention; 73import java.lang.annotation.RetentionPolicy; 74import java.util.ArrayList; 75import java.util.HashSet; 76import java.util.List; 77import java.util.Set; 78 79/** 80 * A class responsible for the life cycle and event handling of the pop-ups over TV view. 81 */ 82// TODO: Put TvTransitionManager into this class. 83@UiThread 84public class TvOverlayManager { 85 private static final String TAG = "TvOverlayManager"; 86 private static final boolean DEBUG = false; 87 public static final String INTRO_TRACKER_LABEL = "Intro dialog"; 88 89 @Retention(RetentionPolicy.SOURCE) 90 @IntDef(flag = true, 91 value = {FLAG_HIDE_OVERLAYS_DEFAULT, FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION, 92 FLAG_HIDE_OVERLAYS_KEEP_SCENE, FLAG_HIDE_OVERLAYS_KEEP_DIALOG, 93 FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS, FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY, 94 FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE, FLAG_HIDE_OVERLAYS_KEEP_MENU, 95 FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT}) 96 public @interface HideOverlayFlag {} 97 // FLAG_HIDE_OVERLAYs must be bitwise exclusive. 98 public static final int FLAG_HIDE_OVERLAYS_DEFAULT = 0b000000000; 99 public static final int FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION = 0b000000010; 100 public static final int FLAG_HIDE_OVERLAYS_KEEP_SCENE = 0b000000100; 101 public static final int FLAG_HIDE_OVERLAYS_KEEP_DIALOG = 0b000001000; 102 public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS = 0b000010000; 103 public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY = 0b000100000; 104 public static final int FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE = 0b001000000; 105 public static final int FLAG_HIDE_OVERLAYS_KEEP_MENU = 0b010000000; 106 public static final int FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT = 0b100000000; 107 108 public static final int MSG_OVERLAY_CLOSED = 1000; 109 110 @Retention(RetentionPolicy.SOURCE) 111 @IntDef(flag = true, 112 value = {OVERLAY_TYPE_NONE, OVERLAY_TYPE_MENU, OVERLAY_TYPE_SIDE_FRAGMENT, 113 OVERLAY_TYPE_DIALOG, OVERLAY_TYPE_GUIDE, OVERLAY_TYPE_SCENE_CHANNEL_BANNER, 114 OVERLAY_TYPE_SCENE_INPUT_BANNER, OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH, 115 OVERLAY_TYPE_SCENE_SELECT_INPUT, OVERLAY_TYPE_FRAGMENT}) 116 private @interface TvOverlayType {} 117 // OVERLAY_TYPEs must be bitwise exclusive. 118 private static final int OVERLAY_TYPE_NONE = 0b000000000; 119 private static final int OVERLAY_TYPE_MENU = 0b000000001; 120 private static final int OVERLAY_TYPE_SIDE_FRAGMENT = 0b000000010; 121 private static final int OVERLAY_TYPE_DIALOG = 0b000000100; 122 private static final int OVERLAY_TYPE_GUIDE = 0b000001000; 123 private static final int OVERLAY_TYPE_SCENE_CHANNEL_BANNER = 0b000010000; 124 private static final int OVERLAY_TYPE_SCENE_INPUT_BANNER = 0b000100000; 125 private static final int OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH = 0b001000000; 126 private static final int OVERLAY_TYPE_SCENE_SELECT_INPUT = 0b010000000; 127 private static final int OVERLAY_TYPE_FRAGMENT = 0b100000000; 128 129 private static final Set<String> AVAILABLE_DIALOG_TAGS = new HashSet<>(); 130 static { 131 AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG); 132 AVAILABLE_DIALOG_TAGS.add(PinDialogFragment.DIALOG_TAG); 133 AVAILABLE_DIALOG_TAGS.add(FullscreenDialogFragment.DIALOG_TAG); 134 AVAILABLE_DIALOG_TAGS.add(SettingsFragment.LicenseActionItem.DIALOG_TAG); 135 AVAILABLE_DIALOG_TAGS.add(RatingsFragment.AttributionItem.DIALOG_TAG); 136 } 137 138 private final MainActivity mMainActivity; 139 private final ChannelTuner mChannelTuner; 140 private final TvTransitionManager mTransitionManager; 141 private final ChannelDataManager mChannelDataManager; 142 private final Menu mMenu; 143 private final SideFragmentManager mSideFragmentManager; 144 private final ProgramGuide mProgramGuide; 145 private final KeypadChannelSwitchView mKeypadChannelSwitchView; 146 private final SelectInputView mSelectInputView; 147 private final ProgramGuideSearchFragment mSearchFragment; 148 private final Tracker mTracker; 149 private SafeDismissDialogFragment mCurrentDialog; 150 private final SetupSourcesFragment mSetupFragment; 151 private boolean mSetupFragmentActive; 152 private final NewSourcesFragment mNewSourcesFragment; 153 private boolean mNewSourcesFragmentActive; 154 private final Handler mHandler = new TvOverlayHandler(this); 155 156 private @TvOverlayType int mOpenedOverlays; 157 158 private List<Runnable> mPendingActions = new ArrayList<>(); 159 160 public TvOverlayManager(MainActivity mainActivity, ChannelTuner channelTuner, 161 KeypadChannelSwitchView keypadChannelSwitchView, 162 ChannelBannerView channelBannerView, InputBannerView inputBannerView, 163 SelectInputView selectInputView, ViewGroup sceneContainer, 164 ProgramGuideSearchFragment searchFragment) { 165 mMainActivity = mainActivity; 166 mChannelTuner = channelTuner; 167 ApplicationSingletons singletons = TvApplication.getSingletons(mainActivity); 168 mChannelDataManager = singletons.getChannelDataManager(); 169 mKeypadChannelSwitchView = keypadChannelSwitchView; 170 mSelectInputView = selectInputView; 171 mSearchFragment = searchFragment; 172 mTracker = singletons.getTracker(); 173 mTransitionManager = new TvTransitionManager(mainActivity, sceneContainer, 174 channelBannerView, inputBannerView, mKeypadChannelSwitchView, selectInputView); 175 mTransitionManager.setListener(new TvTransitionManager.Listener() { 176 @Override 177 public void onSceneChanged(int fromScene, int toScene) { 178 // Call notifyOverlayOpened first so that the listener can know that a new scene 179 // will be opened when the notifyOverlayClosed is called. 180 if (toScene != TvTransitionManager.SCENE_TYPE_EMPTY) { 181 onOverlayOpened(convertSceneToOverlayType(toScene)); 182 } 183 if (fromScene != TvTransitionManager.SCENE_TYPE_EMPTY) { 184 onOverlayClosed(convertSceneToOverlayType(fromScene)); 185 } 186 } 187 }); 188 // Menu 189 MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu); 190 mMenu = new Menu(mainActivity, menuView, new MenuRowFactory(mainActivity), 191 new Menu.OnMenuVisibilityChangeListener() { 192 @Override 193 public void onMenuVisibilityChange(boolean visible) { 194 if (visible) { 195 onOverlayOpened(OVERLAY_TYPE_MENU); 196 } else { 197 onOverlayClosed(OVERLAY_TYPE_MENU); 198 } 199 } 200 }); 201 // Side Fragment 202 mSideFragmentManager = new SideFragmentManager(mainActivity, 203 new Runnable() { 204 @Override 205 public void run() { 206 onOverlayOpened(OVERLAY_TYPE_SIDE_FRAGMENT); 207 hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS); 208 } 209 }, 210 new Runnable() { 211 @Override 212 public void run() { 213 mMainActivity.showChannelBannerIfHiddenBySideFragment(); 214 onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT); 215 } 216 }); 217 // Program Guide 218 Runnable preShowRunnable = new Runnable() { 219 @Override 220 public void run() { 221 onOverlayOpened(OVERLAY_TYPE_GUIDE); 222 } 223 }; 224 Runnable postHideRunnable = new Runnable() { 225 @Override 226 public void run() { 227 onOverlayClosed(OVERLAY_TYPE_GUIDE); 228 } 229 }; 230 mProgramGuide = new ProgramGuide(mainActivity, channelTuner, 231 singletons.getTvInputManagerHelper(), mChannelDataManager, 232 singletons.getProgramDataManager(), singletons.getTracker(), preShowRunnable, 233 postHideRunnable); 234 mSetupFragment = new SetupSourcesFragment(); 235 mSetupFragment.setOnActionClickListener(new OnActionClickListener() { 236 @Override 237 public void onActionClick(String category, int id) { 238 switch (id) { 239 case SetupMultiPaneFragment.ACTION_DONE: 240 closeSetupFragment(true); 241 break; 242 case SetupSourcesFragment.ACTION_PLAY_STORE: 243 mMainActivity.showMerchantCollection(); 244 break; 245 } 246 } 247 }); 248 mSetupFragment.setInputSetupRunnable(new InputSetupRunnable() { 249 @Override 250 public void runInputSetup(TvInputInfo input) { 251 mMainActivity.startSetupActivity(input, true); 252 } 253 }); 254 mNewSourcesFragment = new NewSourcesFragment(); 255 mNewSourcesFragment.setOnActionClickListener(new OnActionClickListener() { 256 @Override 257 public void onActionClick(String category, int id) { 258 switch (id) { 259 case NewSourcesFragment.ACTION_SETUP: 260 closeNewSourcesFragment(false); 261 showSetupFragment(); 262 break; 263 case NewSourcesFragment.ACTION_SKIP: 264 // Don't remove the fragment because new fragment will be replaced with 265 // this fragment. 266 closeNewSourcesFragment(true); 267 break; 268 } 269 } 270 }); 271 } 272 273 /** 274 * A method to release all the allocated resources or unregister listeners. 275 * This is called from {@link MainActivity#onDestroy}. 276 */ 277 public void release() { 278 mMenu.release(); 279 mHandler.removeCallbacksAndMessages(null); 280 } 281 282 /** 283 * Returns the instance of {@link Menu}. 284 */ 285 public Menu getMenu() { 286 return mMenu; 287 } 288 289 /** 290 * Returns the instance of {@link SideFragmentManager}. 291 */ 292 public SideFragmentManager getSideFragmentManager() { 293 return mSideFragmentManager; 294 } 295 296 /** 297 * Returns the currently opened dialog. 298 */ 299 public SafeDismissDialogFragment getCurrentDialog() { 300 return mCurrentDialog; 301 } 302 303 /** 304 * Checks whether the setup fragment is active or not. 305 */ 306 public boolean isSetupFragmentActive() { 307 return mSetupFragmentActive; 308 } 309 310 /** 311 * Checks whether the new sources fragment is active or not. 312 */ 313 public boolean isNewSourcesFragmentActive() { 314 return mNewSourcesFragmentActive; 315 } 316 317 /** 318 * Returns the instance of {@link ProgramGuide}. 319 */ 320 public ProgramGuide getProgramGuide() { 321 return mProgramGuide; 322 } 323 324 /** 325 * Shows the main menu. 326 */ 327 public void showMenu(@MenuShowReason int reason) { 328 if (mChannelTuner != null && mChannelTuner.areAllChannelsLoaded()) { 329 mMenu.show(reason); 330 } 331 } 332 333 /** 334 * Shows the play controller of the menu if the playback is paused. 335 */ 336 public boolean showMenuWithTimeShiftPauseIfNeeded() { 337 if (mMainActivity.getTimeShiftManager().isPaused()) { 338 showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE); 339 return true; 340 } 341 return false; 342 } 343 344 /** 345 * Shows the given dialog. 346 */ 347 public void showDialogFragment(String tag, SafeDismissDialogFragment dialog, 348 boolean keepSidePanelHistory) { 349 int flags = FLAG_HIDE_OVERLAYS_KEEP_DIALOG; 350 if (keepSidePanelHistory) { 351 flags |= FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY; 352 } 353 hideOverlays(flags); 354 // A tag for dialog must be added to AVAILABLE_DIALOG_TAGS to make it launchable from TV. 355 if (!AVAILABLE_DIALOG_TAGS.contains(tag)) { 356 return; 357 } 358 359 Fragment old = mMainActivity.getFragmentManager().findFragmentByTag(tag); 360 // Do not show the dialog if the same kind of dialog is already opened. 361 if (old != null) { 362 return; 363 } 364 365 mCurrentDialog = dialog; 366 dialog.show(mMainActivity.getFragmentManager(), tag); 367 368 // Calling this from SafeDismissDialogFragment.onCreated() might be late 369 // because it takes time for onCreated to be called 370 // and next key events can be handled by MainActivity, not Dialog. 371 onOverlayOpened(OVERLAY_TYPE_DIALOG); 372 } 373 374 private void runAfterSideFragmentsAreClosed(final Runnable runnable) { 375 final FragmentManager manager = mMainActivity.getFragmentManager(); 376 if (mSideFragmentManager.isSidePanelVisible()) { 377 manager.addOnBackStackChangedListener(new OnBackStackChangedListener() { 378 @Override 379 public void onBackStackChanged() { 380 if (manager.getBackStackEntryCount() == 0) { 381 manager.removeOnBackStackChangedListener(this); 382 runnable.run(); 383 } 384 } 385 }); 386 } else { 387 runnable.run(); 388 } 389 } 390 391 private void showFragment(final Fragment fragment) { 392 hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); 393 onOverlayOpened(OVERLAY_TYPE_FRAGMENT); 394 runAfterSideFragmentsAreClosed(new Runnable() { 395 @Override 396 public void run() { 397 mMainActivity.getFragmentManager().beginTransaction() 398 .replace(R.id.fragment_container, fragment).commit(); 399 } 400 }); 401 } 402 403 private void closeFragment(Fragment fragmentToRemove) { 404 onOverlayClosed(OVERLAY_TYPE_FRAGMENT); 405 if (fragmentToRemove != null) { 406 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 407 // In L, NPE happens if there is no next fragment when removing or hiding a fragment 408 // which has an exit transition. b/22631964 409 // A workaround is just replacing with a dummy fragment. 410 mMainActivity.getFragmentManager().beginTransaction() 411 .replace(R.id.fragment_container, new DummyFragment()).commit(); 412 } else { 413 mMainActivity.getFragmentManager().beginTransaction().remove(fragmentToRemove) 414 .commit(); 415 } 416 } 417 } 418 419 /** 420 * Shows setup dialog. 421 */ 422 public void showSetupFragment() { 423 if (DEBUG) Log.d(TAG, "showSetupFragment"); 424 mSetupFragmentActive = true; 425 SetupSourcesFragment.setTheme(R.style.Theme_TV_GuidedStep); 426 mSetupFragment.enableFragmentTransition(SetupFragment.FRAGMENT_ENTER_TRANSITION 427 | SetupFragment.FRAGMENT_EXIT_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION 428 | SetupFragment.FRAGMENT_REENTER_TRANSITION); 429 mSetupFragment.setFragmentTransition(SetupFragment.FRAGMENT_EXIT_TRANSITION, Gravity.END); 430 showFragment(mSetupFragment); 431 } 432 433 // Set removeFragment to false only when the new fragment is going to be shown. 434 private void closeSetupFragment(boolean removeFragment) { 435 if (DEBUG) Log.d(TAG, "closeSetupFragment"); 436 if (!mSetupFragmentActive) { 437 return; 438 } 439 mSetupFragmentActive = false; 440 SetupSourcesFragment.setTheme(SetupSourcesFragment.DEFAULT_THEME); 441 closeFragment(removeFragment ? mSetupFragment : null); 442 if (mChannelDataManager.getChannelCount() == 0) { 443 mMainActivity.finish(); 444 } 445 } 446 447 /** 448 * Shows new sources dialog. 449 */ 450 public void showNewSourcesFragment() { 451 if (DEBUG) Log.d(TAG, "showNewSourcesFragment"); 452 mNewSourcesFragmentActive = true; 453 showFragment(mNewSourcesFragment); 454 } 455 456 // Set removeFragment to false only when the new fragment is going to be shown. 457 private void closeNewSourcesFragment(boolean removeFragment) { 458 if (DEBUG) Log.d(TAG, "closeNewSourcesFragment"); 459 mNewSourcesFragmentActive = false; 460 closeFragment(removeFragment ? mNewSourcesFragment : null); 461 } 462 463 /** 464 * Shows DVR manager. 465 */ 466 public void showDvrManager() { 467 Intent intent = new Intent(mMainActivity, DvrActivity.class); 468 mMainActivity.startActivity(intent); 469 } 470 471 /** 472 * Shows intro dialog. 473 */ 474 public void showIntroDialog() { 475 if (DEBUG) Log.d(TAG,"showIntroDialog"); 476 showDialogFragment(FullscreenDialogFragment.DIALOG_TAG, 477 FullscreenDialogFragment.newInstance(R.layout.intro_dialog, INTRO_TRACKER_LABEL), 478 false); 479 } 480 481 /** 482 * Shows recently watched dialog. 483 */ 484 public void showRecentlyWatchedDialog() { 485 showDialogFragment(RecentlyWatchedDialogFragment.DIALOG_TAG, 486 new RecentlyWatchedDialogFragment(), false); 487 } 488 489 /** 490 * Shows banner view. 491 */ 492 public void showBanner() { 493 mTransitionManager.goToChannelBannerScene(); 494 } 495 496 public void showKeypadChannelSwitch() { 497 hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE 498 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS 499 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG 500 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); 501 mTransitionManager.goToKeypadChannelSwitchScene(); 502 } 503 504 /** 505 * Shows select input view. 506 */ 507 public void showSelectInputView() { 508 hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE); 509 mTransitionManager.goToSelectInputScene(); 510 } 511 512 /** 513 * Initializes animators if animators are not initialized yet. 514 */ 515 public void initAnimatorIfNeeded() { 516 mTransitionManager.initIfNeeded(); 517 } 518 519 /** 520 * It is called when a SafeDismissDialogFragment is destroyed. 521 */ 522 public void onDialogDestroyed() { 523 mCurrentDialog = null; 524 onOverlayClosed(OVERLAY_TYPE_DIALOG); 525 } 526 527 /** 528 * Shows the program guide. 529 */ 530 public void showProgramGuide() { 531 mProgramGuide.show(new Runnable() { 532 @Override 533 public void run() { 534 hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE); 535 } 536 }); 537 } 538 539 /** 540 * Hides all the opened overlays according to the flags. 541 */ 542 // TODO: Add test for this method. 543 public void hideOverlays(@HideOverlayFlag int flags) { 544 if (mMainActivity.needToKeepSetupScreenWhenHidingOverlay()) { 545 flags |= FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT; 546 } 547 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_DIALOG) != 0) { 548 // Keeps the dialog. 549 } else { 550 if (mCurrentDialog != null) { 551 if (mCurrentDialog instanceof PinDialogFragment) { 552 // The result listener of PinDialogFragment could call MenuView when 553 // the dialog is dismissed. In order not to call it, set the result listener 554 // to null. 555 ((PinDialogFragment) mCurrentDialog).setResultListener(null); 556 } 557 mCurrentDialog.dismiss(); 558 } 559 mCurrentDialog = null; 560 } 561 boolean withAnimation = (flags & FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION) == 0; 562 563 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT) == 0) { 564 if (mSetupFragmentActive) { 565 if (!withAnimation) { 566 mSetupFragment.setReturnTransition(null); 567 mSetupFragment.setExitTransition(null); 568 } 569 closeSetupFragment(true); 570 } else if (mNewSourcesFragmentActive) { 571 closeNewSourcesFragment(true); 572 } 573 } 574 575 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_MENU) != 0) { 576 // Keeps the menu. 577 } else { 578 mMenu.hide(withAnimation); 579 } 580 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SCENE) != 0) { 581 // Keeps the current scene. 582 } else { 583 mTransitionManager.goToEmptyScene(withAnimation); 584 } 585 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS) != 0) { 586 // Keeps side panels. 587 } else if (mSideFragmentManager.isSidePanelVisible()) { 588 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY) != 0) { 589 mSideFragmentManager.hideSidePanel(withAnimation); 590 } else { 591 mSideFragmentManager.hideAll(withAnimation); 592 } 593 } 594 if ((flags & FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE) != 0) { 595 // Keep the program guide. 596 } else { 597 mProgramGuide.hide(); 598 } 599 } 600 601 /** 602 * Returns true, if a main view needs to hide informational text. Specifically, when overlay 603 * UIs except banner is shown, the informational text needs to be hidden for clean UI. 604 */ 605 public boolean needHideTextOnMainView() { 606 return mSideFragmentManager.isActive() 607 || getMenu().isActive() 608 || mTransitionManager.isKeypadChannelSwitchActive() 609 || mTransitionManager.isSelectInputActive() 610 || mSetupFragmentActive 611 || mNewSourcesFragmentActive; 612 } 613 614 @TvOverlayType private int convertSceneToOverlayType(@SceneType int sceneType) { 615 switch (sceneType) { 616 case TvTransitionManager.SCENE_TYPE_CHANNEL_BANNER: 617 return OVERLAY_TYPE_SCENE_CHANNEL_BANNER; 618 case TvTransitionManager.SCENE_TYPE_INPUT_BANNER: 619 return OVERLAY_TYPE_SCENE_INPUT_BANNER; 620 case TvTransitionManager.SCENE_TYPE_KEYPAD_CHANNEL_SWITCH: 621 return OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH; 622 case TvTransitionManager.SCENE_TYPE_SELECT_INPUT: 623 return OVERLAY_TYPE_SCENE_SELECT_INPUT; 624 case TvTransitionManager.SCENE_TYPE_EMPTY: 625 default: 626 return OVERLAY_TYPE_NONE; 627 } 628 } 629 630 @UiThread 631 private void onOverlayOpened(@TvOverlayType int overlayType) { 632 if (DEBUG) Log.d(TAG, "Overlay opened: 0b" + Integer.toBinaryString(overlayType)); 633 mOpenedOverlays |= overlayType; 634 if (DEBUG) Log.d(TAG, "Opened overlays: 0b" + Integer.toBinaryString(mOpenedOverlays)); 635 mHandler.removeMessages(MSG_OVERLAY_CLOSED); 636 mMainActivity.updateKeyInputFocus(); 637 } 638 639 @UiThread 640 private void onOverlayClosed(@TvOverlayType int overlayType) { 641 if (DEBUG) Log.d(TAG, "Overlay closed: 0b" + Integer.toBinaryString(overlayType)); 642 mOpenedOverlays &= ~overlayType; 643 if (DEBUG) Log.d(TAG, "Opened overlays: 0b" + Integer.toBinaryString(mOpenedOverlays)); 644 mHandler.removeMessages(MSG_OVERLAY_CLOSED); 645 mMainActivity.updateKeyInputFocus(); 646 // Show the main menu again if there are no pop-ups or banners only. 647 // The main menu should not be shown when the activity is in paused state. 648 boolean menuAboutToShow = false; 649 if (canExecuteCloseAction()) { 650 menuAboutToShow = mMainActivity.getTimeShiftManager().isPaused(); 651 mHandler.sendEmptyMessage(MSG_OVERLAY_CLOSED); 652 } 653 // Don't set screen name to main if the overlay closing is a banner 654 // or if a non banner overlay is still open 655 // or if we just opened the menu 656 if (overlayType != OVERLAY_TYPE_SCENE_CHANNEL_BANNER 657 && overlayType != OVERLAY_TYPE_SCENE_INPUT_BANNER 658 && isOnlyBannerOrNoneOpened() 659 && !menuAboutToShow) { 660 mTracker.sendScreenView(MainActivity.SCREEN_NAME); 661 } 662 } 663 664 private boolean canExecuteCloseAction() { 665 return mMainActivity.isActivityResumed() && isOnlyBannerOrNoneOpened(); 666 } 667 668 private boolean isOnlyBannerOrNoneOpened() { 669 return (mOpenedOverlays & ~OVERLAY_TYPE_SCENE_CHANNEL_BANNER 670 & ~OVERLAY_TYPE_SCENE_INPUT_BANNER) == 0; 671 } 672 673 /** 674 * Runs a given {@code action} after all the overlays are closed. 675 */ 676 @UiThread 677 public void runAfterOverlaysAreClosed(Runnable action) { 678 if (canExecuteCloseAction()) { 679 action.run(); 680 } else { 681 mPendingActions.add(action); 682 } 683 } 684 685 /** 686 * Handles the onUserInteraction event of the {@link MainActivity}. 687 */ 688 public void onUserInteraction() { 689 if (mSideFragmentManager.isActive()) { 690 mSideFragmentManager.scheduleHideAll(); 691 } else if (mMenu.isActive()) { 692 mMenu.scheduleHide(); 693 } else if (mProgramGuide.isActive()) { 694 mProgramGuide.scheduleHide(); 695 } 696 } 697 698 /** 699 * Handles the onKeyDown event of the {@link MainActivity}. 700 */ 701 @KeyHandlerResultType public int onKeyDown(int keyCode, KeyEvent event) { 702 if (mCurrentDialog != null) { 703 // Consumes the keys while a Dialog is creating. 704 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 705 } 706 // Handle media key here because it is related to the menu. 707 if (isMediaStartKey(keyCode)) { 708 // Consumes the keys which may trigger system's default music player. 709 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 710 } 711 if (mMenu.isActive() || mSideFragmentManager.isActive() || mProgramGuide.isActive() 712 || mSetupFragmentActive || mNewSourcesFragmentActive) { 713 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 714 } 715 if (mTransitionManager.isKeypadChannelSwitchActive()) { 716 return mKeypadChannelSwitchView.onKeyDown(keyCode, event) ? 717 MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED 718 : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; 719 } 720 if (mTransitionManager.isSelectInputActive()) { 721 return mSelectInputView.onKeyDown(keyCode, event) ? 722 MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED 723 : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; 724 } 725 return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH; 726 } 727 728 /** 729 * Handles the onKeyUp event of the {@link MainActivity}. 730 */ 731 @KeyHandlerResultType public int onKeyUp(int keyCode, KeyEvent event) { 732 // Handle media key here because it is related to the menu. 733 if (isMediaStartKey(keyCode)) { 734 // The media key should not be passed up to the system in any cases. 735 if (mCurrentDialog != null || mProgramGuide.isActive() 736 || mSideFragmentManager.isActive() 737 || mSearchFragment.isVisible() 738 || mTransitionManager.isKeypadChannelSwitchActive() 739 || mTransitionManager.isSelectInputActive() 740 || mSetupFragmentActive 741 || mNewSourcesFragmentActive) { 742 // Do not handle media key when any pop-ups which can handle keys are active. 743 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 744 } 745 TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager(); 746 if (!timeShiftManager.isAvailable()) { 747 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 748 } 749 switch (keyCode) { 750 case KeyEvent.KEYCODE_MEDIA_PLAY: 751 timeShiftManager.play(); 752 showMenu(Menu.REASON_PLAY_CONTROLS_PLAY); 753 break; 754 case KeyEvent.KEYCODE_MEDIA_PAUSE: 755 timeShiftManager.pause(); 756 showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE); 757 break; 758 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 759 timeShiftManager.togglePlayPause(); 760 showMenu(Menu.REASON_PLAY_CONTROLS_PLAY_PAUSE); 761 break; 762 case KeyEvent.KEYCODE_MEDIA_REWIND: 763 timeShiftManager.rewind(); 764 showMenu(Menu.REASON_PLAY_CONTROLS_REWIND); 765 break; 766 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 767 timeShiftManager.fastForward(); 768 showMenu(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD); 769 break; 770 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 771 timeShiftManager.jumpToPrevious(); 772 showMenu(Menu.REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS); 773 break; 774 case KeyEvent.KEYCODE_MEDIA_NEXT: 775 timeShiftManager.jumpToNext(); 776 showMenu(Menu.REASON_PLAY_CONTROLS_JUMP_TO_NEXT); 777 break; 778 default: 779 // Does nothing. 780 break; 781 } 782 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 783 } 784 if (keyCode == KeyEvent.KEYCODE_I || keyCode == KeyEvent.KEYCODE_TV_INPUT) { 785 if (mTransitionManager.isSelectInputActive()) { 786 mSelectInputView.onKeyUp(keyCode, event); 787 } else { 788 showSelectInputView(); 789 } 790 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 791 } 792 if (mCurrentDialog != null) { 793 // Consumes the keys while a Dialog is showing. 794 // This can be happen while a Dialog isn't created yet. 795 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 796 } 797 if (mProgramGuide.isActive()) { 798 if (keyCode == KeyEvent.KEYCODE_BACK) { 799 mProgramGuide.onBackPressed(); 800 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 801 } 802 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 803 } 804 if (mSideFragmentManager.isActive()) { 805 if (keyCode == KeyEvent.KEYCODE_BACK 806 || mSideFragmentManager.isHideKeyForCurrentPanel(keyCode)) { 807 mSideFragmentManager.popSideFragment(); 808 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 809 } 810 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 811 } 812 if (mMenu.isActive() || mTransitionManager.isSceneActive()) { 813 if (keyCode == KeyEvent.KEYCODE_BACK) { 814 TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager(); 815 if (timeShiftManager.isPaused()) { 816 timeShiftManager.play(); 817 } 818 hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS 819 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG 820 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); 821 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 822 } 823 if (mMenu.isActive()) { 824 if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) { 825 mMainActivity.showKeypadChannelSwitchView(keyCode); 826 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 827 } 828 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 829 } 830 } 831 if (mTransitionManager.isKeypadChannelSwitchActive()) { 832 if (keyCode == KeyEvent.KEYCODE_BACK) { 833 mTransitionManager.goToEmptyScene(true); 834 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 835 } 836 return mKeypadChannelSwitchView.onKeyUp(keyCode, event) ? 837 MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED 838 : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; 839 } 840 if (mTransitionManager.isSelectInputActive()) { 841 if (keyCode == KeyEvent.KEYCODE_BACK) { 842 mTransitionManager.goToEmptyScene(true); 843 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 844 } 845 return mSelectInputView.onKeyUp(keyCode, event) ? 846 MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED 847 : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; 848 } 849 if (mSetupFragmentActive) { 850 if (keyCode == KeyEvent.KEYCODE_BACK) { 851 closeSetupFragment(true); 852 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 853 } 854 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 855 } 856 if (mNewSourcesFragmentActive) { 857 if (keyCode == KeyEvent.KEYCODE_BACK) { 858 closeNewSourcesFragment(true); 859 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; 860 } 861 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; 862 } 863 return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH; 864 } 865 866 /** 867 * Checks whether the given {@code keyCode} can start the system's music app or not. 868 */ 869 private static boolean isMediaStartKey(int keyCode) { 870 switch (keyCode) { 871 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 872 case KeyEvent.KEYCODE_MEDIA_PLAY: 873 case KeyEvent.KEYCODE_MEDIA_PAUSE: 874 case KeyEvent.KEYCODE_MEDIA_NEXT: 875 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 876 case KeyEvent.KEYCODE_MEDIA_REWIND: 877 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 878 return true; 879 } 880 return false; 881 } 882 883 private static class TvOverlayHandler extends WeakHandler<TvOverlayManager> { 884 public TvOverlayHandler(TvOverlayManager ref) { 885 super(ref); 886 } 887 888 @Override 889 public void handleMessage(Message msg, @NonNull TvOverlayManager tvOverlayManager) { 890 switch (msg.what) { 891 case MSG_OVERLAY_CLOSED: 892 if (!tvOverlayManager.canExecuteCloseAction()) { 893 return; 894 } 895 if (tvOverlayManager.showMenuWithTimeShiftPauseIfNeeded()) { 896 return; 897 } 898 if (!tvOverlayManager.mPendingActions.isEmpty()) { 899 Runnable action = tvOverlayManager.mPendingActions.get(0); 900 tvOverlayManager.mPendingActions.remove(action); 901 action.run(); 902 } 903 break; 904 } 905 } 906 } 907 908 /** 909 * Dummny class for the workaround of b/22631964. See {@link #closeFragment}. 910 */ 911 public static class DummyFragment extends Fragment { 912 @Override 913 public @Nullable View onCreateView(LayoutInflater inflater, ViewGroup container, 914 Bundle savedInstanceState) { 915 final View v = new Space(inflater.getContext()); 916 v.setVisibility(View.GONE); 917 return v; 918 } 919 } 920} 921