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