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
17package com.android.tv.ui;
18
19import android.app.Fragment;
20import android.app.FragmentManager;
21import android.app.FragmentManager.OnBackStackChangedListener;
22import android.content.Intent;
23import android.media.tv.TvInputInfo;
24import android.os.Build;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.Message;
28import android.support.annotation.IntDef;
29import android.support.annotation.NonNull;
30import android.support.annotation.Nullable;
31import android.support.annotation.UiThread;
32import android.support.v4.os.BuildCompat;
33import android.util.Log;
34import android.view.Gravity;
35import android.view.KeyEvent;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39import android.widget.Space;
40
41import com.android.tv.ApplicationSingletons;
42import com.android.tv.ChannelTuner;
43import com.android.tv.MainActivity;
44import com.android.tv.MainActivity.KeyHandlerResultType;
45import com.android.tv.R;
46import com.android.tv.TimeShiftManager;
47import com.android.tv.TvApplication;
48import com.android.tv.analytics.Tracker;
49import com.android.tv.common.WeakHandler;
50import com.android.tv.common.feature.CommonFeatures;
51import com.android.tv.common.ui.setup.OnActionClickListener;
52import com.android.tv.common.ui.setup.SetupFragment;
53import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
54import com.android.tv.data.ChannelDataManager;
55import com.android.tv.dialog.FullscreenDialogFragment;
56import com.android.tv.dialog.PinDialogFragment;
57import com.android.tv.dialog.RecentlyWatchedDialogFragment;
58import com.android.tv.dialog.SafeDismissDialogFragment;
59import com.android.tv.dvr.DvrDataManager;
60import com.android.tv.dvr.ui.DvrActivity;
61import com.android.tv.dvr.ui.HalfSizedDialogFragment;
62import com.android.tv.guide.ProgramGuide;
63import com.android.tv.menu.Menu;
64import com.android.tv.menu.Menu.MenuShowReason;
65import com.android.tv.menu.MenuRowFactory;
66import com.android.tv.menu.MenuView;
67import com.android.tv.onboarding.NewSourcesFragment;
68import com.android.tv.onboarding.SetupSourcesFragment;
69import com.android.tv.onboarding.SetupSourcesFragment.InputSetupRunnable;
70import com.android.tv.search.ProgramGuideSearchFragment;
71import com.android.tv.ui.TvTransitionManager.SceneType;
72import com.android.tv.ui.sidepanel.SettingsFragment;
73import com.android.tv.ui.sidepanel.SideFragmentManager;
74import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment;
75
76import java.lang.annotation.Retention;
77import java.lang.annotation.RetentionPolicy;
78import java.util.ArrayList;
79import java.util.HashSet;
80import java.util.List;
81import 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
88public 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