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