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