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