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