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