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