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