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