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.TvInputInfo;
24 import android.os.Build;
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.Nullable;
31 import android.support.annotation.UiThread;
32 import android.support.v4.os.BuildCompat;
33 import android.util.Log;
34 import android.view.Gravity;
35 import android.view.KeyEvent;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.widget.Space;
40 
41 import com.android.tv.ApplicationSingletons;
42 import com.android.tv.ChannelTuner;
43 import com.android.tv.MainActivity;
44 import com.android.tv.MainActivity.KeyHandlerResultType;
45 import com.android.tv.R;
46 import com.android.tv.TimeShiftManager;
47 import com.android.tv.TvApplication;
48 import com.android.tv.analytics.Tracker;
49 import com.android.tv.common.WeakHandler;
50 import com.android.tv.common.feature.CommonFeatures;
51 import com.android.tv.common.ui.setup.OnActionClickListener;
52 import com.android.tv.common.ui.setup.SetupFragment;
53 import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
54 import com.android.tv.data.ChannelDataManager;
55 import com.android.tv.dialog.FullscreenDialogFragment;
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.dvr.DvrDataManager;
60 import com.android.tv.dvr.ui.DvrActivity;
61 import com.android.tv.dvr.ui.HalfSizedDialogFragment;
62 import com.android.tv.guide.ProgramGuide;
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.onboarding.NewSourcesFragment;
68 import com.android.tv.onboarding.SetupSourcesFragment;
69 import com.android.tv.onboarding.SetupSourcesFragment.InputSetupRunnable;
70 import com.android.tv.search.ProgramGuideSearchFragment;
71 import com.android.tv.ui.TvTransitionManager.SceneType;
72 import com.android.tv.ui.sidepanel.SettingsFragment;
73 import com.android.tv.ui.sidepanel.SideFragmentManager;
74 import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment;
75 
76 import java.lang.annotation.Retention;
77 import java.lang.annotation.RetentionPolicy;
78 import java.util.ArrayList;
79 import java.util.HashSet;
80 import java.util.List;
81 import java.util.Set;
82 
83 /**
84  * A class responsible for the life cycle and event handling of the pop-ups over TV view.
85  */
86 // TODO: Put TvTransitionManager into this class.
87 @UiThread
88 public class TvOverlayManager {
89     private static final String TAG = "TvOverlayManager";
90     private static final boolean DEBUG = false;
91     public static final String INTRO_TRACKER_LABEL = "Intro dialog";
92 
93     @Retention(RetentionPolicy.SOURCE)
94     @IntDef(flag = true,
95             value = {FLAG_HIDE_OVERLAYS_DEFAULT, FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION,
96                     FLAG_HIDE_OVERLAYS_KEEP_SCENE, FLAG_HIDE_OVERLAYS_KEEP_DIALOG,
97                     FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS, FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY,
98                     FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE, FLAG_HIDE_OVERLAYS_KEEP_MENU,
99                     FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT})
100     public @interface HideOverlayFlag {}
101     // FLAG_HIDE_OVERLAYs must be bitwise exclusive.
102     public static final int FLAG_HIDE_OVERLAYS_DEFAULT =                 0b000000000;
103     public static final int FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION =       0b000000010;
104     public static final int FLAG_HIDE_OVERLAYS_KEEP_SCENE =              0b000000100;
105     public static final int FLAG_HIDE_OVERLAYS_KEEP_DIALOG =             0b000001000;
106     public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS =        0b000010000;
107     public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY = 0b000100000;
108     public static final int FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE =      0b001000000;
109     public static final int FLAG_HIDE_OVERLAYS_KEEP_MENU =               0b010000000;
110     public static final int FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT =           0b100000000;
111 
112     public static final int MSG_OVERLAY_CLOSED = 1000;
113 
114     @Retention(RetentionPolicy.SOURCE)
115     @IntDef(flag = true,
116             value = {OVERLAY_TYPE_NONE, OVERLAY_TYPE_MENU, OVERLAY_TYPE_SIDE_FRAGMENT,
117                     OVERLAY_TYPE_DIALOG, OVERLAY_TYPE_GUIDE, OVERLAY_TYPE_SCENE_CHANNEL_BANNER,
118                     OVERLAY_TYPE_SCENE_INPUT_BANNER, OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH,
119                     OVERLAY_TYPE_SCENE_SELECT_INPUT, OVERLAY_TYPE_FRAGMENT})
120     private @interface TvOverlayType {}
121     // OVERLAY_TYPEs must be bitwise exclusive.
122     private static final int OVERLAY_TYPE_NONE =                        0b000000000;
123     private static final int OVERLAY_TYPE_MENU =                        0b000000001;
124     private static final int OVERLAY_TYPE_SIDE_FRAGMENT =               0b000000010;
125     private static final int OVERLAY_TYPE_DIALOG =                      0b000000100;
126     private static final int OVERLAY_TYPE_GUIDE =                       0b000001000;
127     private static final int OVERLAY_TYPE_SCENE_CHANNEL_BANNER =        0b000010000;
128     private static final int OVERLAY_TYPE_SCENE_INPUT_BANNER =          0b000100000;
129     private static final int OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH = 0b001000000;
130     private static final int OVERLAY_TYPE_SCENE_SELECT_INPUT =          0b010000000;
131     private static final int OVERLAY_TYPE_FRAGMENT =                    0b100000000;
132 
133     private static final Set<String> AVAILABLE_DIALOG_TAGS = new HashSet<>();
134     static {
135         AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG);
136         AVAILABLE_DIALOG_TAGS.add(PinDialogFragment.DIALOG_TAG);
137         AVAILABLE_DIALOG_TAGS.add(FullscreenDialogFragment.DIALOG_TAG);
138         AVAILABLE_DIALOG_TAGS.add(SettingsFragment.LicenseActionItem.DIALOG_TAG);
139         AVAILABLE_DIALOG_TAGS.add(RatingsFragment.AttributionItem.DIALOG_TAG);
140         AVAILABLE_DIALOG_TAGS.add(HalfSizedDialogFragment.DIALOG_TAG);
141     }
142 
143     private final MainActivity mMainActivity;
144     private final ChannelTuner mChannelTuner;
145     private final TvTransitionManager mTransitionManager;
146     private final ChannelDataManager mChannelDataManager;
147     private final Menu mMenu;
148     private final SideFragmentManager mSideFragmentManager;
149     private final ProgramGuide mProgramGuide;
150     private final KeypadChannelSwitchView mKeypadChannelSwitchView;
151     private final SelectInputView mSelectInputView;
152     private final ProgramGuideSearchFragment mSearchFragment;
153     private final Tracker mTracker;
154     private SafeDismissDialogFragment mCurrentDialog;
155     private final SetupSourcesFragment mSetupFragment;
156     private boolean mSetupFragmentActive;
157     private final NewSourcesFragment mNewSourcesFragment;
158     private boolean mNewSourcesFragmentActive;
159     private final Handler mHandler = new TvOverlayHandler(this);
160 
161     private @TvOverlayType int mOpenedOverlays;
162 
163     private final List<Runnable> mPendingActions = new ArrayList<>();
164 
TvOverlayManager(MainActivity mainActivity, ChannelTuner channelTuner, KeypadChannelSwitchView keypadChannelSwitchView, ChannelBannerView channelBannerView, InputBannerView inputBannerView, SelectInputView selectInputView, ViewGroup sceneContainer, ProgramGuideSearchFragment searchFragment)165     public TvOverlayManager(MainActivity mainActivity, ChannelTuner channelTuner,
166             KeypadChannelSwitchView keypadChannelSwitchView,
167             ChannelBannerView channelBannerView, InputBannerView inputBannerView,
168             SelectInputView selectInputView, ViewGroup sceneContainer,
169             ProgramGuideSearchFragment searchFragment) {
170         mMainActivity = mainActivity;
171         mChannelTuner = channelTuner;
172         ApplicationSingletons singletons = TvApplication.getSingletons(mainActivity);
173         mChannelDataManager = singletons.getChannelDataManager();
174         mKeypadChannelSwitchView = keypadChannelSwitchView;
175         mSelectInputView = selectInputView;
176         mSearchFragment = searchFragment;
177         mTracker = singletons.getTracker();
178         mTransitionManager = new TvTransitionManager(mainActivity, sceneContainer,
179                 channelBannerView, inputBannerView, mKeypadChannelSwitchView, selectInputView);
180         mTransitionManager.setListener(new TvTransitionManager.Listener() {
181             @Override
182             public void onSceneChanged(int fromScene, int toScene) {
183                 // Call notifyOverlayOpened first so that the listener can know that a new scene
184                 // will be opened when the notifyOverlayClosed is called.
185                 if (toScene != TvTransitionManager.SCENE_TYPE_EMPTY) {
186                     onOverlayOpened(convertSceneToOverlayType(toScene));
187                 }
188                 if (fromScene != TvTransitionManager.SCENE_TYPE_EMPTY) {
189                     onOverlayClosed(convertSceneToOverlayType(fromScene));
190                 }
191             }
192         });
193         // Menu
194         MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu);
195         mMenu = new Menu(mainActivity, menuView, new MenuRowFactory(mainActivity),
196                 new Menu.OnMenuVisibilityChangeListener() {
197                     @Override
198                     public void onMenuVisibilityChange(boolean visible) {
199                         if (visible) {
200                             onOverlayOpened(OVERLAY_TYPE_MENU);
201                         } else {
202                             onOverlayClosed(OVERLAY_TYPE_MENU);
203                         }
204                     }
205                 });
206         // Side Fragment
207         mSideFragmentManager = new SideFragmentManager(mainActivity,
208                 new Runnable() {
209                     @Override
210                     public void run() {
211                         onOverlayOpened(OVERLAY_TYPE_SIDE_FRAGMENT);
212                         hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS);
213                     }
214                 },
215                 new Runnable() {
216                     @Override
217                     public void run() {
218                         mMainActivity.showChannelBannerIfHiddenBySideFragment();
219                         onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT);
220                     }
221                 });
222         // Program Guide
223         Runnable preShowRunnable = new Runnable() {
224             @Override
225             public void run() {
226                 onOverlayOpened(OVERLAY_TYPE_GUIDE);
227             }
228         };
229         Runnable postHideRunnable = new Runnable() {
230             @Override
231             public void run() {
232                 onOverlayClosed(OVERLAY_TYPE_GUIDE);
233             }
234         };
235         DvrDataManager dvrDataManager =
236                 CommonFeatures.DVR.isEnabled(mainActivity) && BuildCompat.isAtLeastN() ? singletons
237                 .getDvrDataManager() : null;
238         mProgramGuide = new ProgramGuide(mainActivity, channelTuner,
239                 singletons.getTvInputManagerHelper(), mChannelDataManager,
240                 singletons.getProgramDataManager(), dvrDataManager, singletons.getTracker(),
241                 preShowRunnable,
242                 postHideRunnable);
243         mSetupFragment = new SetupSourcesFragment();
244         mSetupFragment.setOnActionClickListener(new OnActionClickListener() {
245             @Override
246             public void onActionClick(String category, int id) {
247                 switch (id) {
248                     case SetupMultiPaneFragment.ACTION_DONE:
249                         closeSetupFragment(true);
250                         break;
251                     case SetupSourcesFragment.ACTION_PLAY_STORE:
252                         mMainActivity.showMerchantCollection();
253                         break;
254                 }
255             }
256         });
257         mSetupFragment.setInputSetupRunnable(new InputSetupRunnable() {
258             @Override
259             public void runInputSetup(TvInputInfo input) {
260                 mMainActivity.startSetupActivity(input, true);
261             }
262         });
263         mNewSourcesFragment = new NewSourcesFragment();
264         mNewSourcesFragment.setOnActionClickListener(new OnActionClickListener() {
265             @Override
266             public void onActionClick(String category, int id) {
267                 switch (id) {
268                     case NewSourcesFragment.ACTION_SETUP:
269                         closeNewSourcesFragment(false);
270                         showSetupFragment();
271                         break;
272                     case NewSourcesFragment.ACTION_SKIP:
273                         // Don't remove the fragment because new fragment will be replaced with
274                         // this fragment.
275                         closeNewSourcesFragment(true);
276                         break;
277                 }
278             }
279         });
280     }
281 
282     /**
283      * A method to release all the allocated resources or unregister listeners.
284      * This is called from {@link MainActivity#onDestroy}.
285      */
release()286     public void release() {
287         mMenu.release();
288         mHandler.removeCallbacksAndMessages(null);
289     }
290 
291     /**
292      * Returns the instance of {@link Menu}.
293      */
getMenu()294     public Menu getMenu() {
295         return mMenu;
296     }
297 
298     /**
299      * Returns the instance of {@link SideFragmentManager}.
300      */
getSideFragmentManager()301     public SideFragmentManager getSideFragmentManager() {
302         return mSideFragmentManager;
303     }
304 
305     /**
306      * Returns the currently opened dialog.
307      */
getCurrentDialog()308     public SafeDismissDialogFragment getCurrentDialog() {
309         return mCurrentDialog;
310     }
311 
312     /**
313      * Checks whether the setup fragment is active or not.
314      */
isSetupFragmentActive()315     public boolean isSetupFragmentActive() {
316         return mSetupFragmentActive;
317     }
318 
319     /**
320      * Checks whether the new sources fragment is active or not.
321      */
isNewSourcesFragmentActive()322     public boolean isNewSourcesFragmentActive() {
323         return mNewSourcesFragmentActive;
324     }
325 
326     /**
327      * Returns the instance of {@link ProgramGuide}.
328      */
getProgramGuide()329     public ProgramGuide getProgramGuide() {
330         return mProgramGuide;
331     }
332 
333     /**
334      * Shows the main menu.
335      */
showMenu(@enuShowReason int reason)336     public void showMenu(@MenuShowReason int reason) {
337         if (mChannelTuner != null && mChannelTuner.areAllChannelsLoaded()) {
338             mMenu.show(reason);
339         }
340     }
341 
342     /**
343      * Shows the play controller of the menu if the playback is paused.
344      */
showMenuWithTimeShiftPauseIfNeeded()345     public boolean showMenuWithTimeShiftPauseIfNeeded() {
346         if (mMainActivity.getTimeShiftManager().isPaused()) {
347             showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE);
348             return true;
349         }
350         return false;
351     }
352 
353     /**
354      * Shows the given dialog.
355      */
showDialogFragment(String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory)356     public void showDialogFragment(String tag, SafeDismissDialogFragment dialog,
357             boolean keepSidePanelHistory) {
358         showDialogFragment(tag, dialog, keepSidePanelHistory, false);
359     }
360 
showDialogFragment(String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory, boolean keepProgramGuide)361     public void showDialogFragment(String tag, SafeDismissDialogFragment dialog,
362             boolean keepSidePanelHistory, boolean keepProgramGuide) {
363         int flags = FLAG_HIDE_OVERLAYS_KEEP_DIALOG;
364         if (keepSidePanelHistory) {
365             flags |= FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY;
366         }
367         if (keepProgramGuide) {
368             flags |= FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE;
369         }
370         hideOverlays(flags);
371         // A tag for dialog must be added to AVAILABLE_DIALOG_TAGS to make it launchable from TV.
372         if (!AVAILABLE_DIALOG_TAGS.contains(tag)) {
373             return;
374         }
375 
376         Fragment old = mMainActivity.getFragmentManager().findFragmentByTag(tag);
377         // Do not show the dialog if the same kind of dialog is already opened.
378         if (old != null) {
379             return;
380         }
381 
382         mCurrentDialog = dialog;
383         dialog.show(mMainActivity.getFragmentManager(), tag);
384 
385         // Calling this from SafeDismissDialogFragment.onCreated() might be late
386         // because it takes time for onCreated to be called
387         // and next key events can be handled by MainActivity, not Dialog.
388         onOverlayOpened(OVERLAY_TYPE_DIALOG);
389     }
390 
runAfterSideFragmentsAreClosed(final Runnable runnable)391     private void runAfterSideFragmentsAreClosed(final Runnable runnable) {
392         final FragmentManager manager = mMainActivity.getFragmentManager();
393         if (mSideFragmentManager.isSidePanelVisible()) {
394             manager.addOnBackStackChangedListener(new OnBackStackChangedListener() {
395                 @Override
396                 public void onBackStackChanged() {
397                     if (manager.getBackStackEntryCount() == 0) {
398                         manager.removeOnBackStackChangedListener(this);
399                         runnable.run();
400                     }
401                 }
402             });
403         } else {
404             runnable.run();
405         }
406     }
407 
showFragment(final Fragment fragment)408     private void showFragment(final Fragment fragment) {
409         hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
410         onOverlayOpened(OVERLAY_TYPE_FRAGMENT);
411         runAfterSideFragmentsAreClosed(new Runnable() {
412             @Override
413             public void run() {
414                 mMainActivity.getFragmentManager().beginTransaction()
415                         .replace(R.id.fragment_container, fragment).commit();
416             }
417         });
418     }
419 
closeFragment(Fragment fragmentToRemove)420     private void closeFragment(Fragment fragmentToRemove) {
421         onOverlayClosed(OVERLAY_TYPE_FRAGMENT);
422         if (fragmentToRemove != null) {
423             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
424                 // In L, NPE happens if there is no next fragment when removing or hiding a fragment
425                 // which has an exit transition. b/22631964
426                 // A workaround is just replacing with a dummy fragment.
427                 mMainActivity.getFragmentManager().beginTransaction()
428                         .replace(R.id.fragment_container, new DummyFragment()).commit();
429             } else {
430                 mMainActivity.getFragmentManager().beginTransaction().remove(fragmentToRemove)
431                         .commit();
432             }
433         }
434     }
435 
436     /**
437      * Shows setup dialog.
438      */
showSetupFragment()439     public void showSetupFragment() {
440         if (DEBUG) Log.d(TAG, "showSetupFragment");
441         mSetupFragmentActive = true;
442         mSetupFragment.enableFragmentTransition(SetupFragment.FRAGMENT_ENTER_TRANSITION
443                 | SetupFragment.FRAGMENT_EXIT_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION
444                 | SetupFragment.FRAGMENT_REENTER_TRANSITION);
445         mSetupFragment.setFragmentTransition(SetupFragment.FRAGMENT_EXIT_TRANSITION, Gravity.END);
446         showFragment(mSetupFragment);
447     }
448 
449     // Set removeFragment to false only when the new fragment is going to be shown.
closeSetupFragment(boolean removeFragment)450     private void closeSetupFragment(boolean removeFragment) {
451         if (DEBUG) Log.d(TAG, "closeSetupFragment");
452         if (!mSetupFragmentActive) {
453             return;
454         }
455         mSetupFragmentActive = false;
456         closeFragment(removeFragment ? mSetupFragment : null);
457         if (mChannelDataManager.getChannelCount() == 0) {
458             mMainActivity.finish();
459         }
460     }
461 
462     /**
463      * Shows new sources dialog.
464      */
showNewSourcesFragment()465     public void showNewSourcesFragment() {
466         if (DEBUG) Log.d(TAG, "showNewSourcesFragment");
467         mNewSourcesFragmentActive = true;
468         showFragment(mNewSourcesFragment);
469     }
470 
471     // Set removeFragment to false only when the new fragment is going to be shown.
closeNewSourcesFragment(boolean removeFragment)472     private void closeNewSourcesFragment(boolean removeFragment) {
473         if (DEBUG) Log.d(TAG, "closeNewSourcesFragment");
474         mNewSourcesFragmentActive = false;
475         closeFragment(removeFragment ? mNewSourcesFragment : null);
476     }
477 
478     /**
479      * Shows DVR manager.
480      */
showDvrManager()481     public void showDvrManager() {
482         Intent intent = new Intent(mMainActivity, DvrActivity.class);
483         mMainActivity.startActivity(intent);
484     }
485 
486     /**
487      * Shows intro dialog.
488      */
showIntroDialog()489     public void showIntroDialog() {
490         if (DEBUG) Log.d(TAG,"showIntroDialog");
491         showDialogFragment(FullscreenDialogFragment.DIALOG_TAG,
492                 FullscreenDialogFragment.newInstance(R.layout.intro_dialog, INTRO_TRACKER_LABEL),
493                 false);
494     }
495 
496     /**
497      * Shows recently watched dialog.
498      */
showRecentlyWatchedDialog()499     public void showRecentlyWatchedDialog() {
500         showDialogFragment(RecentlyWatchedDialogFragment.DIALOG_TAG,
501                 new RecentlyWatchedDialogFragment(), false);
502     }
503 
504     /**
505      * Shows banner view.
506      */
showBanner()507     public void showBanner() {
508         mTransitionManager.goToChannelBannerScene();
509     }
510 
showKeypadChannelSwitch()511     public void showKeypadChannelSwitch() {
512         hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE
513                 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
514                 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
515                 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
516         mTransitionManager.goToKeypadChannelSwitchScene();
517     }
518 
519     /**
520      * Shows select input view.
521      */
showSelectInputView()522     public void showSelectInputView() {
523         hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE);
524         mTransitionManager.goToSelectInputScene();
525     }
526 
527     /**
528      * Initializes animators if animators are not initialized yet.
529      */
initAnimatorIfNeeded()530     public void initAnimatorIfNeeded() {
531         mTransitionManager.initIfNeeded();
532     }
533 
534     /**
535      * It is called when a SafeDismissDialogFragment is destroyed.
536      */
onDialogDestroyed()537     public void onDialogDestroyed() {
538         mCurrentDialog = null;
539         onOverlayClosed(OVERLAY_TYPE_DIALOG);
540     }
541 
542     /**
543      * Shows the program guide.
544      */
showProgramGuide()545     public void showProgramGuide() {
546         mProgramGuide.show(new Runnable() {
547             @Override
548             public void run() {
549                 hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE);
550             }
551         });
552     }
553 
554     /**
555      * Hides all the opened overlays according to the flags.
556      */
557     // TODO: Add test for this method.
hideOverlays(@ideOverlayFlag int flags)558     public void hideOverlays(@HideOverlayFlag int flags) {
559         if (mMainActivity.needToKeepSetupScreenWhenHidingOverlay()) {
560             flags |= FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT;
561         }
562         if ((flags & FLAG_HIDE_OVERLAYS_KEEP_DIALOG) != 0) {
563             // Keeps the dialog.
564         } else {
565             if (mCurrentDialog != null) {
566                 if (mCurrentDialog instanceof PinDialogFragment) {
567                     // The result listener of PinDialogFragment could call MenuView when
568                     // the dialog is dismissed. In order not to call it, set the result listener
569                     // to null.
570                     ((PinDialogFragment) mCurrentDialog).setResultListener(null);
571                 }
572                 mCurrentDialog.dismiss();
573             }
574             mCurrentDialog = null;
575         }
576         boolean withAnimation = (flags & FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION) == 0;
577 
578         if ((flags & FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT) == 0) {
579             if (mSetupFragmentActive) {
580                 if (!withAnimation) {
581                     mSetupFragment.setReturnTransition(null);
582                     mSetupFragment.setExitTransition(null);
583                 }
584                 closeSetupFragment(true);
585             } else if (mNewSourcesFragmentActive) {
586                 closeNewSourcesFragment(true);
587             }
588         }
589 
590         if ((flags & FLAG_HIDE_OVERLAYS_KEEP_MENU) != 0) {
591             // Keeps the menu.
592         } else {
593             mMenu.hide(withAnimation);
594         }
595         if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SCENE) != 0) {
596             // Keeps the current scene.
597         } else {
598             mTransitionManager.goToEmptyScene(withAnimation);
599         }
600         if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS) != 0) {
601             // Keeps side panels.
602         } else if (mSideFragmentManager.isSidePanelVisible()) {
603             if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY) != 0) {
604                 mSideFragmentManager.hideSidePanel(withAnimation);
605             } else {
606                 mSideFragmentManager.hideAll(withAnimation);
607             }
608         }
609         if ((flags & FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE) != 0) {
610             // Keep the program guide.
611         } else {
612             mProgramGuide.hide();
613         }
614     }
615 
616     /**
617      * Returns true, if a main view needs to hide informational text. Specifically, when overlay
618      * UIs except banner is shown, the informational text needs to be hidden for clean UI.
619      */
needHideTextOnMainView()620     public boolean needHideTextOnMainView() {
621         return mSideFragmentManager.isActive()
622                 || getMenu().isActive()
623                 || mTransitionManager.isKeypadChannelSwitchActive()
624                 || mTransitionManager.isSelectInputActive()
625                 || mSetupFragmentActive
626                 || mNewSourcesFragmentActive;
627     }
628 
convertSceneToOverlayType(@ceneType int sceneType)629     @TvOverlayType private int convertSceneToOverlayType(@SceneType int sceneType) {
630         switch (sceneType) {
631             case TvTransitionManager.SCENE_TYPE_CHANNEL_BANNER:
632                 return OVERLAY_TYPE_SCENE_CHANNEL_BANNER;
633             case TvTransitionManager.SCENE_TYPE_INPUT_BANNER:
634                 return OVERLAY_TYPE_SCENE_INPUT_BANNER;
635             case TvTransitionManager.SCENE_TYPE_KEYPAD_CHANNEL_SWITCH:
636                 return OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH;
637             case TvTransitionManager.SCENE_TYPE_SELECT_INPUT:
638                 return OVERLAY_TYPE_SCENE_SELECT_INPUT;
639             case TvTransitionManager.SCENE_TYPE_EMPTY:
640             default:
641                 return OVERLAY_TYPE_NONE;
642         }
643     }
644 
645     @UiThread
onOverlayOpened(@vOverlayType int overlayType)646     private void onOverlayOpened(@TvOverlayType int overlayType) {
647         if (DEBUG) Log.d(TAG, "Overlay opened:  0b" + Integer.toBinaryString(overlayType));
648         mOpenedOverlays |= overlayType;
649         if (DEBUG) Log.d(TAG, "Opened overlays: 0b" + Integer.toBinaryString(mOpenedOverlays));
650         mHandler.removeMessages(MSG_OVERLAY_CLOSED);
651         mMainActivity.updateKeyInputFocus();
652     }
653 
654     @UiThread
onOverlayClosed(@vOverlayType int overlayType)655     private void onOverlayClosed(@TvOverlayType int overlayType) {
656         if (DEBUG) Log.d(TAG, "Overlay closed:  0b" + Integer.toBinaryString(overlayType));
657         mOpenedOverlays &= ~overlayType;
658         if (DEBUG) Log.d(TAG, "Opened overlays: 0b" + Integer.toBinaryString(mOpenedOverlays));
659         mHandler.removeMessages(MSG_OVERLAY_CLOSED);
660         mMainActivity.updateKeyInputFocus();
661         // Show the main menu again if there are no pop-ups or banners only.
662         // The main menu should not be shown when the activity is in paused state.
663         boolean menuAboutToShow = false;
664         if (canExecuteCloseAction()) {
665             menuAboutToShow = mMainActivity.getTimeShiftManager().isPaused();
666             mHandler.sendEmptyMessage(MSG_OVERLAY_CLOSED);
667         }
668         // Don't set screen name to main if the overlay closing is a banner
669         // or if a non banner overlay is still open
670         // or if we just opened the menu
671         if (overlayType != OVERLAY_TYPE_SCENE_CHANNEL_BANNER
672                 && overlayType != OVERLAY_TYPE_SCENE_INPUT_BANNER
673                 && isOnlyBannerOrNoneOpened()
674                 && !menuAboutToShow) {
675             mTracker.sendScreenView(MainActivity.SCREEN_NAME);
676         }
677     }
678 
canExecuteCloseAction()679     private boolean canExecuteCloseAction() {
680         return mMainActivity.isActivityResumed() && isOnlyBannerOrNoneOpened();
681     }
682 
isOnlyBannerOrNoneOpened()683     private boolean isOnlyBannerOrNoneOpened() {
684         return (mOpenedOverlays & ~OVERLAY_TYPE_SCENE_CHANNEL_BANNER
685                 & ~OVERLAY_TYPE_SCENE_INPUT_BANNER) == 0;
686     }
687 
688     /**
689      * Runs a given {@code action} after all the overlays are closed.
690      */
691     @UiThread
runAfterOverlaysAreClosed(Runnable action)692     public void runAfterOverlaysAreClosed(Runnable action) {
693         if (canExecuteCloseAction()) {
694             action.run();
695         } else {
696             mPendingActions.add(action);
697         }
698     }
699 
700     /**
701      * Handles the onUserInteraction event of the {@link MainActivity}.
702      */
onUserInteraction()703     public void onUserInteraction() {
704         if (mSideFragmentManager.isActive()) {
705             mSideFragmentManager.scheduleHideAll();
706         } else if (mMenu.isActive()) {
707             mMenu.scheduleHide();
708         } else if (mProgramGuide.isActive()) {
709             mProgramGuide.scheduleHide();
710         }
711     }
712 
713     /**
714      * Handles the onKeyDown event of the {@link MainActivity}.
715      */
onKeyDown(int keyCode, KeyEvent event)716     @KeyHandlerResultType public int onKeyDown(int keyCode, KeyEvent event) {
717         if (mCurrentDialog != null) {
718             // Consumes the keys while a Dialog is creating.
719             return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
720         }
721         // Handle media key here because it is related to the menu.
722         if (isMediaStartKey(keyCode)) {
723             // Consumes the keys which may trigger system's default music player.
724             return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
725         }
726         if (mMenu.isActive() || mSideFragmentManager.isActive() || mProgramGuide.isActive()
727                 || mSetupFragmentActive || mNewSourcesFragmentActive) {
728             return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
729         }
730         if (mTransitionManager.isKeypadChannelSwitchActive()) {
731             return mKeypadChannelSwitchView.onKeyDown(keyCode, event) ?
732                     MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
733                     : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED;
734         }
735         if (mTransitionManager.isSelectInputActive()) {
736             return mSelectInputView.onKeyDown(keyCode, event) ?
737                     MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
738                     : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED;
739         }
740         return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH;
741     }
742 
743     /**
744      * Handles the onKeyUp event of the {@link MainActivity}.
745      */
onKeyUp(int keyCode, KeyEvent event)746     @KeyHandlerResultType public int onKeyUp(int keyCode, KeyEvent event) {
747         // Handle media key here because it is related to the menu.
748         if (isMediaStartKey(keyCode)) {
749             // The media key should not be passed up to the system in any cases.
750             if (mCurrentDialog != null || mProgramGuide.isActive()
751                     || mSideFragmentManager.isActive()
752                     || mSearchFragment.isVisible()
753                     || mTransitionManager.isKeypadChannelSwitchActive()
754                     || mTransitionManager.isSelectInputActive()
755                     || mSetupFragmentActive
756                     || mNewSourcesFragmentActive) {
757                 // Do not handle media key when any pop-ups which can handle keys are active.
758                 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
759             }
760             TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager();
761             if (!timeShiftManager.isAvailable()) {
762                 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
763             }
764             switch (keyCode) {
765                 case KeyEvent.KEYCODE_MEDIA_PLAY:
766                     timeShiftManager.play();
767                     showMenu(Menu.REASON_PLAY_CONTROLS_PLAY);
768                     break;
769                 case KeyEvent.KEYCODE_MEDIA_PAUSE:
770                     timeShiftManager.pause();
771                     showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE);
772                     break;
773                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
774                     timeShiftManager.togglePlayPause();
775                     showMenu(Menu.REASON_PLAY_CONTROLS_PLAY_PAUSE);
776                     break;
777                 case KeyEvent.KEYCODE_MEDIA_REWIND:
778                     timeShiftManager.rewind();
779                     showMenu(Menu.REASON_PLAY_CONTROLS_REWIND);
780                     break;
781                 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
782                     timeShiftManager.fastForward();
783                     showMenu(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD);
784                     break;
785                 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
786                     timeShiftManager.jumpToPrevious();
787                     showMenu(Menu.REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS);
788                     break;
789                 case KeyEvent.KEYCODE_MEDIA_NEXT:
790                     timeShiftManager.jumpToNext();
791                     showMenu(Menu.REASON_PLAY_CONTROLS_JUMP_TO_NEXT);
792                     break;
793                 default:
794                     // Does nothing.
795                     break;
796             }
797             return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
798         }
799         if (keyCode == KeyEvent.KEYCODE_I || keyCode == KeyEvent.KEYCODE_TV_INPUT) {
800             if (mTransitionManager.isSelectInputActive()) {
801                 mSelectInputView.onKeyUp(keyCode, event);
802             } else {
803                 showSelectInputView();
804             }
805             return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
806         }
807         if (mCurrentDialog != null) {
808             // Consumes the keys while a Dialog is showing.
809             // This can be happen while a Dialog isn't created yet.
810             return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
811         }
812         if (mProgramGuide.isActive()) {
813             if (keyCode == KeyEvent.KEYCODE_BACK) {
814                 mProgramGuide.onBackPressed();
815                 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
816             }
817             return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
818         }
819         if (mSideFragmentManager.isActive()) {
820             if (keyCode == KeyEvent.KEYCODE_BACK
821                     || mSideFragmentManager.isHideKeyForCurrentPanel(keyCode)) {
822                 mSideFragmentManager.popSideFragment();
823                 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
824             }
825             return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
826         }
827         if (mMenu.isActive() || mTransitionManager.isSceneActive()) {
828             if (keyCode == KeyEvent.KEYCODE_BACK) {
829                 TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager();
830                 if (timeShiftManager.isPaused()) {
831                     timeShiftManager.play();
832                 }
833                 hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
834                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
835                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
836                 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
837             }
838             if (mMenu.isActive()) {
839                 if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) {
840                     mMainActivity.showKeypadChannelSwitchView(keyCode);
841                     return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
842                 }
843                 return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
844             }
845         }
846         if (mTransitionManager.isKeypadChannelSwitchActive()) {
847             if (keyCode == KeyEvent.KEYCODE_BACK) {
848                 mTransitionManager.goToEmptyScene(true);
849                 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
850             }
851             return mKeypadChannelSwitchView.onKeyUp(keyCode, event) ?
852                     MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
853                     : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED;
854         }
855         if (mTransitionManager.isSelectInputActive()) {
856             if (keyCode == KeyEvent.KEYCODE_BACK) {
857                 mTransitionManager.goToEmptyScene(true);
858                 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
859             }
860             return mSelectInputView.onKeyUp(keyCode, event) ?
861                     MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
862                     : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED;
863         }
864         if (mSetupFragmentActive) {
865             if (keyCode == KeyEvent.KEYCODE_BACK) {
866                 closeSetupFragment(true);
867                 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
868             }
869             return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
870         }
871         if (mNewSourcesFragmentActive) {
872             if (keyCode == KeyEvent.KEYCODE_BACK) {
873                 closeNewSourcesFragment(true);
874                 return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
875             }
876             return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
877         }
878         return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH;
879     }
880 
881     /**
882      * Checks whether the given {@code keyCode} can start the system's music app or not.
883      */
isMediaStartKey(int keyCode)884     private static boolean isMediaStartKey(int keyCode) {
885         switch (keyCode) {
886             case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
887             case KeyEvent.KEYCODE_MEDIA_PLAY:
888             case KeyEvent.KEYCODE_MEDIA_PAUSE:
889             case KeyEvent.KEYCODE_MEDIA_NEXT:
890             case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
891             case KeyEvent.KEYCODE_MEDIA_REWIND:
892             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
893                 return true;
894         }
895         return false;
896     }
897 
898     private static class TvOverlayHandler extends WeakHandler<TvOverlayManager> {
TvOverlayHandler(TvOverlayManager ref)899         public TvOverlayHandler(TvOverlayManager ref) {
900             super(ref);
901         }
902 
903         @Override
handleMessage(Message msg, @NonNull TvOverlayManager tvOverlayManager)904         public void handleMessage(Message msg, @NonNull TvOverlayManager tvOverlayManager) {
905             switch (msg.what) {
906                 case MSG_OVERLAY_CLOSED:
907                     if (!tvOverlayManager.canExecuteCloseAction()) {
908                         return;
909                     }
910                     if (tvOverlayManager.showMenuWithTimeShiftPauseIfNeeded()) {
911                         return;
912                     }
913                     if (!tvOverlayManager.mPendingActions.isEmpty()) {
914                         Runnable action = tvOverlayManager.mPendingActions.get(0);
915                         tvOverlayManager.mPendingActions.remove(action);
916                         action.run();
917                     }
918                     break;
919             }
920         }
921     }
922 
923     /**
924      * Dummny class for the workaround of b/22631964. See {@link #closeFragment}.
925      */
926     public static class DummyFragment extends Fragment {
927         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)928         public @Nullable View onCreateView(LayoutInflater inflater, ViewGroup container,
929                 Bundle savedInstanceState) {
930             final View v = new Space(inflater.getContext());
931             v.setVisibility(View.GONE);
932             return v;
933         }
934     }
935 }
936