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