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