| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tv.ui; |
| |
| import android.app.Fragment; |
| import android.app.FragmentManager; |
| import android.app.FragmentManager.OnBackStackChangedListener; |
| import android.content.Intent; |
| import android.media.tv.TvContentRating; |
| import android.media.tv.TvInputInfo; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.support.annotation.IntDef; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.UiThread; |
| import android.util.Log; |
| import android.view.Gravity; |
| import android.view.KeyEvent; |
| import android.view.ViewGroup; |
| import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; |
| |
| import com.android.tv.ChannelTuner; |
| import com.android.tv.MainActivity; |
| import com.android.tv.MainActivity.KeyHandlerResultType; |
| import com.android.tv.R; |
| import com.android.tv.TimeShiftManager; |
| import com.android.tv.TvOptionsManager; |
| import com.android.tv.TvSingletons; |
| import com.android.tv.analytics.Tracker; |
| import com.android.tv.common.WeakHandler; |
| import com.android.tv.common.feature.CommonFeatures; |
| import com.android.tv.common.ui.setup.OnActionClickListener; |
| import com.android.tv.common.ui.setup.SetupFragment; |
| import com.android.tv.common.ui.setup.SetupMultiPaneFragment; |
| import com.android.tv.data.ChannelDataManager; |
| import com.android.tv.data.ProgramDataManager; |
| import com.android.tv.dialog.DvrHistoryDialogFragment; |
| import com.android.tv.dialog.FullscreenDialogFragment; |
| import com.android.tv.dialog.HalfSizedDialogFragment; |
| import com.android.tv.dialog.PinDialogFragment; |
| import com.android.tv.dialog.RecentlyWatchedDialogFragment; |
| import com.android.tv.dialog.SafeDismissDialogFragment; |
| import com.android.tv.dvr.DvrDataManager; |
| import com.android.tv.dvr.ui.browse.DvrBrowseActivity; |
| import com.android.tv.guide.ProgramGuide; |
| import com.android.tv.license.LicenseDialogFragment; |
| import com.android.tv.menu.Menu; |
| import com.android.tv.menu.Menu.MenuShowReason; |
| import com.android.tv.menu.MenuRowFactory; |
| import com.android.tv.menu.MenuView; |
| import com.android.tv.menu.TvOptionsRowAdapter; |
| import com.android.tv.onboarding.NewSourcesFragment; |
| import com.android.tv.onboarding.SetupSourcesFragment; |
| import com.android.tv.search.ProgramGuideSearchFragment; |
| import com.android.tv.ui.TvTransitionManager.SceneType; |
| import com.android.tv.ui.sidepanel.SideFragmentManager; |
| import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment; |
| import com.android.tv.util.TvInputManagerHelper; |
| |
| import com.google.auto.factory.AutoFactory; |
| import com.google.auto.factory.Provided; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Queue; |
| import java.util.Set; |
| |
| /** A class responsible for the life cycle and event handling of the pop-ups over TV view. */ |
| @UiThread |
| @AutoFactory |
| public class TvOverlayManager implements AccessibilityStateChangeListener { |
| private static final String TAG = "TvOverlayManager"; |
| private static final boolean DEBUG = false; |
| private static final String INTRO_TRACKER_LABEL = "Intro dialog"; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef( |
| flag = true, |
| value = { |
| FLAG_HIDE_OVERLAYS_DEFAULT, |
| FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION, |
| FLAG_HIDE_OVERLAYS_KEEP_SCENE, |
| FLAG_HIDE_OVERLAYS_KEEP_DIALOG, |
| FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS, |
| FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY, |
| FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE, |
| FLAG_HIDE_OVERLAYS_KEEP_MENU, |
| FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT |
| }) |
| private @interface HideOverlayFlag {} |
| // FLAG_HIDE_OVERLAYs must be bitwise exclusive. |
| public static final int FLAG_HIDE_OVERLAYS_DEFAULT = 0b000000000; |
| public static final int FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION = 0b000000010; |
| public static final int FLAG_HIDE_OVERLAYS_KEEP_SCENE = 0b000000100; |
| public static final int FLAG_HIDE_OVERLAYS_KEEP_DIALOG = 0b000001000; |
| public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS = 0b000010000; |
| public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY = 0b000100000; |
| public static final int FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE = 0b001000000; |
| public static final int FLAG_HIDE_OVERLAYS_KEEP_MENU = 0b010000000; |
| public static final int FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT = 0b100000000; |
| |
| private static final int MSG_OVERLAY_CLOSED = 1000; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef( |
| flag = true, |
| value = { |
| OVERLAY_TYPE_NONE, |
| OVERLAY_TYPE_MENU, |
| OVERLAY_TYPE_SIDE_FRAGMENT, |
| OVERLAY_TYPE_DIALOG, |
| OVERLAY_TYPE_GUIDE, |
| OVERLAY_TYPE_SCENE_CHANNEL_BANNER, |
| OVERLAY_TYPE_SCENE_INPUT_BANNER, |
| OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH, |
| OVERLAY_TYPE_SCENE_SELECT_INPUT, |
| OVERLAY_TYPE_FRAGMENT |
| }) |
| private @interface TvOverlayType {} |
| // OVERLAY_TYPEs must be bitwise exclusive. |
| /** The overlay type which indicates that there are no overlays. */ |
| private static final int OVERLAY_TYPE_NONE = 0b000000000; |
| /** The overlay type for menu. */ |
| private static final int OVERLAY_TYPE_MENU = 0b000000001; |
| /** The overlay type for the side fragment. */ |
| private static final int OVERLAY_TYPE_SIDE_FRAGMENT = 0b000000010; |
| /** The overlay type for dialog fragment. */ |
| private static final int OVERLAY_TYPE_DIALOG = 0b000000100; |
| /** The overlay type for program guide. */ |
| private static final int OVERLAY_TYPE_GUIDE = 0b000001000; |
| /** The overlay type for channel banner. */ |
| private static final int OVERLAY_TYPE_SCENE_CHANNEL_BANNER = 0b000010000; |
| /** The overlay type for input banner. */ |
| private static final int OVERLAY_TYPE_SCENE_INPUT_BANNER = 0b000100000; |
| /** The overlay type for keypad channel switch view. */ |
| private static final int OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH = 0b001000000; |
| /** The overlay type for select input view. */ |
| private static final int OVERLAY_TYPE_SCENE_SELECT_INPUT = 0b010000000; |
| /** The overlay type for fragment other than the side fragment and dialog fragment. */ |
| private static final int OVERLAY_TYPE_FRAGMENT = 0b100000000; |
| // Used for the padded print of the overlay type. |
| private static final int NUM_OVERLAY_TYPES = 9; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({ |
| UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW, |
| UPDATE_CHANNEL_BANNER_REASON_TUNE, |
| UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST, |
| UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO, |
| UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK, |
| UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO |
| }) |
| private @interface ChannelBannerUpdateReason {} |
| /** Updates channel banner because the channel banner is forced to show. */ |
| public static final int UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW = 1; |
| /** Updates channel banner because of tuning. */ |
| public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE = 2; |
| /** Updates channel banner because of fast tuning. */ |
| public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST = 3; |
| /** Updates channel banner because of info updating. */ |
| public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO = 4; |
| /** Updates channel banner because the current watched channel is locked or unlocked. */ |
| public static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5; |
| /** Updates channel banner because of stream info updating. */ |
| public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO = 6; |
| /** Updates channel banner because of channel signal updating. */ |
| public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_SIGNAL_STRENGTH = 7; |
| |
| private static final String FRAGMENT_TAG_SETUP_SOURCES = "tag_setup_sources"; |
| private static final String FRAGMENT_TAG_NEW_SOURCES = "tag_new_sources"; |
| |
| private static final Set<String> AVAILABLE_DIALOG_TAGS = new HashSet<>(); |
| |
| static { |
| AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG); |
| AVAILABLE_DIALOG_TAGS.add(DvrHistoryDialogFragment.DIALOG_TAG); |
| AVAILABLE_DIALOG_TAGS.add(PinDialogFragment.DIALOG_TAG); |
| AVAILABLE_DIALOG_TAGS.add(FullscreenDialogFragment.DIALOG_TAG); |
| AVAILABLE_DIALOG_TAGS.add(LicenseDialogFragment.DIALOG_TAG); |
| AVAILABLE_DIALOG_TAGS.add(RatingsFragment.AttributionItem.DIALOG_TAG); |
| AVAILABLE_DIALOG_TAGS.add(HalfSizedDialogFragment.DIALOG_TAG); |
| } |
| |
| private final MainActivity mMainActivity; |
| private final ChannelTuner mChannelTuner; |
| private final TvTransitionManager mTransitionManager; |
| private final ChannelDataManager mChannelDataManager; |
| private final TvInputManagerHelper mInputManager; |
| private final Menu mMenu; |
| private final TunableTvView mTvView; |
| private final SideFragmentManager mSideFragmentManager; |
| private final ProgramGuide mProgramGuide; |
| private final ChannelBannerView mChannelBannerView; |
| private final KeypadChannelSwitchView mKeypadChannelSwitchView; |
| private final SelectInputView mSelectInputView; |
| private final ProgramGuideSearchFragment mSearchFragment; |
| private final Tracker mTracker; |
| private SafeDismissDialogFragment mCurrentDialog; |
| private boolean mSetupFragmentActive; |
| private boolean mNewSourcesFragmentActive; |
| private boolean mChannelBannerHiddenBySideFragment; |
| private final Handler mHandler = new TvOverlayHandler(this); |
| |
| private @TvOverlayType int mOpenedOverlays; |
| |
| private final List<Runnable> mPendingActions = new ArrayList<>(); |
| private final Queue<PendingDialogAction> mPendingDialogActionQueue = new LinkedList<>(); |
| private final TvOptionsRowAdapter.Factory mTvOptionsRowAdapterFactory; |
| |
| private OnBackStackChangedListener mOnBackStackChangedListener; |
| |
| public 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) { |
| mMainActivity = mainActivity; |
| mChannelTuner = channelTuner; |
| this.mTvOptionsRowAdapterFactory = mTvOptionsRowAdapterFactory; |
| TvSingletons singletons = TvSingletons.getSingletons(mainActivity); |
| mChannelDataManager = channelDataManager; |
| mInputManager = tvInputManager; |
| mTvView = tvView; |
| mChannelBannerView = channelBannerView; |
| mKeypadChannelSwitchView = keypadChannelSwitchView; |
| mSelectInputView = selectInputView; |
| mSearchFragment = searchFragment; |
| mTracker = singletons.getTracker(); |
| mTransitionManager = |
| new TvTransitionManager( |
| mainActivity, |
| sceneContainer, |
| channelBannerView, |
| inputBannerView, |
| mKeypadChannelSwitchView, |
| selectInputView); |
| mTransitionManager.setListener( |
| new TvTransitionManager.Listener() { |
| @Override |
| public void onSceneChanged(int fromScene, int toScene) { |
| // Call onOverlayOpened first so that the listener can know that a new scene |
| // will be opened when the onOverlayClosed is called. |
| if (toScene != TvTransitionManager.SCENE_TYPE_EMPTY) { |
| onOverlayOpened(convertSceneToOverlayType(toScene)); |
| } |
| if (fromScene != TvTransitionManager.SCENE_TYPE_EMPTY) { |
| onOverlayClosed(convertSceneToOverlayType(fromScene)); |
| } |
| } |
| }); |
| // Menu |
| MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu); |
| mMenu = |
| new Menu( |
| mainActivity, |
| tvView, |
| optionsManager, |
| menuView, |
| new MenuRowFactory(mainActivity, tvView, this.mTvOptionsRowAdapterFactory), |
| new Menu.OnMenuVisibilityChangeListener() { |
| @Override |
| public void onMenuVisibilityChange(boolean visible) { |
| if (visible) { |
| onOverlayOpened(OVERLAY_TYPE_MENU); |
| } else { |
| onOverlayClosed(OVERLAY_TYPE_MENU); |
| } |
| } |
| }); |
| mMenu.setChannelTuner(mChannelTuner); |
| // Side Fragment |
| mSideFragmentManager = |
| new SideFragmentManager( |
| mainActivity, |
| () -> { |
| onOverlayOpened(OVERLAY_TYPE_SIDE_FRAGMENT); |
| hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS); |
| }, |
| () -> { |
| showChannelBannerIfHiddenBySideFragment(); |
| onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT); |
| }); |
| // Program Guide |
| Runnable preShowRunnable = () -> onOverlayOpened(OVERLAY_TYPE_GUIDE); |
| Runnable postHideRunnable = () -> onOverlayClosed(OVERLAY_TYPE_GUIDE); |
| DvrDataManager dvrDataManager = |
| CommonFeatures.DVR.isEnabled(mainActivity) ? singletons.getDvrDataManager() : null; |
| mProgramGuide = |
| new ProgramGuide( |
| mainActivity, |
| channelTuner, |
| mInputManager, |
| mChannelDataManager, |
| programDataManager, |
| dvrDataManager, |
| singletons.getDvrScheduleManager(), |
| singletons.getTracker(), |
| preShowRunnable, |
| postHideRunnable); |
| mMainActivity.addOnActionClickListener( |
| new OnActionClickListener() { |
| @Override |
| public boolean onActionClick(String category, int id, Bundle params) { |
| switch (category) { |
| case SetupSourcesFragment.ACTION_CATEGORY: |
| switch (id) { |
| case SetupMultiPaneFragment.ACTION_DONE: |
| closeSetupFragment(true); |
| return true; |
| case SetupSourcesFragment.ACTION_ONLINE_STORE: |
| mMainActivity.showMerchantCollection(); |
| return true; |
| case SetupSourcesFragment.ACTION_SETUP_INPUT: |
| { |
| String inputId = |
| params.getString( |
| SetupSourcesFragment |
| .ACTION_PARAM_KEY_INPUT_ID); |
| TvInputInfo input = |
| mInputManager.getTvInputInfo(inputId); |
| mMainActivity.startSetupActivity(input, true); |
| return true; |
| } |
| } |
| break; |
| case NewSourcesFragment.ACTION_CATEOGRY: |
| switch (id) { |
| case NewSourcesFragment.ACTION_SETUP: |
| closeNewSourcesFragment(false); |
| showSetupFragment(); |
| return true; |
| case NewSourcesFragment.ACTION_SKIP: |
| // Don't remove the fragment because new fragment will be |
| // replaced |
| // with this fragment. |
| closeNewSourcesFragment(true); |
| return true; |
| } |
| break; |
| } |
| return false; |
| } |
| }); |
| } |
| |
| /** |
| * A method to release all the allocated resources or unregister listeners. This is called from |
| * {@link MainActivity#onDestroy}. |
| */ |
| public void release() { |
| mMenu.release(); |
| mHandler.removeCallbacksAndMessages(null); |
| if (mKeypadChannelSwitchView != null) { |
| mKeypadChannelSwitchView.setChannels(null); |
| } |
| } |
| |
| /** Returns the instance of {@link Menu}. */ |
| public Menu getMenu() { |
| return mMenu; |
| } |
| |
| /** Returns the instance of {@link SideFragmentManager}. */ |
| public SideFragmentManager getSideFragmentManager() { |
| return mSideFragmentManager; |
| } |
| |
| /** Returns the currently opened dialog. */ |
| public SafeDismissDialogFragment getCurrentDialog() { |
| return mCurrentDialog; |
| } |
| |
| /** Checks whether the setup fragment is active or not. */ |
| public boolean isSetupFragmentActive() { |
| // "getSetupSourcesFragment() != null" doesn't return the correct state. That's because, |
| // when we call showSetupFragment(), we need to put off showing the fragment until the side |
| // fragment is closed. Until then, getSetupSourcesFragment() returns null. So we need |
| // to keep additional variable which indicates if showSetupFragment() is called. |
| return mSetupFragmentActive; |
| } |
| |
| private Fragment getSetupSourcesFragment() { |
| return mMainActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG_SETUP_SOURCES); |
| } |
| |
| /** Checks whether the new sources fragment is active or not. */ |
| public boolean isNewSourcesFragmentActive() { |
| // See the comment in "isSetupFragmentActive". |
| return mNewSourcesFragmentActive; |
| } |
| |
| private Fragment getNewSourcesFragment() { |
| return mMainActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG_NEW_SOURCES); |
| } |
| |
| /** Returns the instance of {@link ProgramGuide}. */ |
| public ProgramGuide getProgramGuide() { |
| return mProgramGuide; |
| } |
| |
| /** Shows the main menu. */ |
| public void showMenu(@MenuShowReason int reason) { |
| if (mChannelTuner != null && mChannelTuner.areAllChannelsLoaded()) { |
| mMenu.show(reason); |
| } |
| } |
| |
| /** Shows the play controller of the menu if the playback is paused. */ |
| public boolean showMenuWithTimeShiftPauseIfNeeded() { |
| if (mMainActivity.getTimeShiftManager().isPaused()) { |
| showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE); |
| return true; |
| } |
| return false; |
| } |
| |
| /** Shows the given dialog. */ |
| public void showDialogFragment( |
| String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory) { |
| showDialogFragment(tag, dialog, keepSidePanelHistory, false); |
| } |
| |
| public void showDialogFragment( |
| String tag, |
| SafeDismissDialogFragment dialog, |
| boolean keepSidePanelHistory, |
| boolean keepProgramGuide) { |
| int flags = FLAG_HIDE_OVERLAYS_KEEP_DIALOG; |
| if (keepSidePanelHistory) { |
| flags |= FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY; |
| } |
| if (keepProgramGuide) { |
| flags |= FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE; |
| } |
| hideOverlays(flags); |
| // A tag for dialog must be added to AVAILABLE_DIALOG_TAGS to make it launchable from TV. |
| if (!AVAILABLE_DIALOG_TAGS.contains(tag)) { |
| return; |
| } |
| |
| // Do not open two dialogs at the same time. |
| if (mCurrentDialog != null) { |
| mPendingDialogActionQueue.offer( |
| new PendingDialogAction(tag, dialog, keepSidePanelHistory, keepProgramGuide)); |
| return; |
| } |
| |
| mCurrentDialog = dialog; |
| dialog.show(mMainActivity.getFragmentManager(), tag); |
| |
| // Calling this from SafeDismissDialogFragment.onCreated() might be late |
| // because it takes time for onCreated to be called |
| // and next key events can be handled by MainActivity, not Dialog. |
| onOverlayOpened(OVERLAY_TYPE_DIALOG); |
| } |
| |
| /** |
| * Should be called by {@link MainActivity} when the currently browsable channels are updated. |
| */ |
| public void onBrowsableChannelsUpdated() { |
| mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList()); |
| } |
| |
| private void runAfterSideFragmentsAreClosed(final Runnable runnable) { |
| if (mSideFragmentManager.isSidePanelVisible()) { |
| // When the side panel is closing, it closes all the fragments, so the new fragment |
| // should be opened after the side fragment becomes invisible. |
| final FragmentManager manager = mMainActivity.getFragmentManager(); |
| mOnBackStackChangedListener = |
| new OnBackStackChangedListener() { |
| @Override |
| public void onBackStackChanged() { |
| if (manager.getBackStackEntryCount() == 0) { |
| manager.removeOnBackStackChangedListener(this); |
| mOnBackStackChangedListener = null; |
| runnable.run(); |
| } |
| } |
| }; |
| manager.addOnBackStackChangedListener(mOnBackStackChangedListener); |
| } else { |
| runnable.run(); |
| } |
| } |
| |
| private void showFragment(final Fragment fragment, final String tag) { |
| hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); |
| onOverlayOpened(OVERLAY_TYPE_FRAGMENT); |
| runAfterSideFragmentsAreClosed( |
| () -> { |
| if (DEBUG) Log.d(TAG, "showFragment(" + fragment + ")"); |
| mMainActivity |
| .getFragmentManager() |
| .beginTransaction() |
| .replace(R.id.fragment_container, fragment, tag) |
| .commit(); |
| }); |
| } |
| |
| private void closeFragment(String fragmentTagToRemove) { |
| if (DEBUG) Log.d(TAG, "closeFragment(" + fragmentTagToRemove + ")"); |
| onOverlayClosed(OVERLAY_TYPE_FRAGMENT); |
| if (fragmentTagToRemove != null) { |
| Fragment fragmentToRemove = |
| mMainActivity.getFragmentManager().findFragmentByTag(fragmentTagToRemove); |
| if (fragmentToRemove == null) { |
| // If the fragment has not been added to the fragment manager yet, just remove the |
| // listener not to add the fragment. This is needed because the side fragment is |
| // closed asynchronously. |
| mMainActivity |
| .getFragmentManager() |
| .removeOnBackStackChangedListener(mOnBackStackChangedListener); |
| mOnBackStackChangedListener = null; |
| } else { |
| mMainActivity |
| .getFragmentManager() |
| .beginTransaction() |
| .remove(fragmentToRemove) |
| .commit(); |
| } |
| } |
| } |
| |
| /** Shows setup dialog. */ |
| public void showSetupFragment() { |
| if (DEBUG) Log.d(TAG, "showSetupFragment"); |
| mSetupFragmentActive = true; |
| SetupSourcesFragment setupFragment = new SetupSourcesFragment(); |
| setupFragment.enableFragmentTransition( |
| SetupFragment.FRAGMENT_ENTER_TRANSITION |
| | SetupFragment.FRAGMENT_EXIT_TRANSITION |
| | SetupFragment.FRAGMENT_RETURN_TRANSITION |
| | SetupFragment.FRAGMENT_REENTER_TRANSITION); |
| setupFragment.setFragmentTransition(SetupFragment.FRAGMENT_EXIT_TRANSITION, Gravity.END); |
| showFragment(setupFragment, FRAGMENT_TAG_SETUP_SOURCES); |
| } |
| |
| // Set removeFragment to false only when the new fragment is going to be shown. |
| private void closeSetupFragment(boolean removeFragment) { |
| if (DEBUG) Log.d(TAG, "closeSetupFragment"); |
| if (!mSetupFragmentActive) { |
| return; |
| } |
| mSetupFragmentActive = false; |
| closeFragment(removeFragment ? FRAGMENT_TAG_SETUP_SOURCES : null); |
| if (mChannelDataManager.getChannelCount() == 0) { |
| if (DEBUG) Log.d(TAG, "Finishing MainActivity because there are no channels."); |
| mMainActivity.finish(); |
| } |
| } |
| |
| /** Shows new sources dialog. */ |
| public void showNewSourcesFragment() { |
| if (DEBUG) Log.d(TAG, "showNewSourcesFragment"); |
| mNewSourcesFragmentActive = true; |
| showFragment(new NewSourcesFragment(), FRAGMENT_TAG_NEW_SOURCES); |
| } |
| |
| // Set removeFragment to false only when the new fragment is going to be shown. |
| private void closeNewSourcesFragment(boolean removeFragment) { |
| if (DEBUG) Log.d(TAG, "closeNewSourcesFragment"); |
| if (!mNewSourcesFragmentActive) { |
| return; |
| } |
| mNewSourcesFragmentActive = false; |
| closeFragment(removeFragment ? FRAGMENT_TAG_NEW_SOURCES : null); |
| } |
| |
| /** Shows DVR manager. */ |
| public void showDvrManager() { |
| Intent intent = new Intent(mMainActivity, DvrBrowseActivity.class); |
| mMainActivity.startActivity(intent); |
| } |
| |
| /** Shows intro dialog. */ |
| public void showIntroDialog() { |
| if (DEBUG) Log.d(TAG, "showIntroDialog"); |
| showDialogFragment( |
| FullscreenDialogFragment.DIALOG_TAG, |
| FullscreenDialogFragment.newInstance(R.layout.intro_dialog, INTRO_TRACKER_LABEL), |
| false); |
| } |
| |
| /** Shows recently watched dialog. */ |
| public void showRecentlyWatchedDialog() { |
| showDialogFragment( |
| RecentlyWatchedDialogFragment.DIALOG_TAG, |
| new RecentlyWatchedDialogFragment(), |
| false); |
| } |
| |
| /** Shows DVR history dialog. */ |
| public void showDvrHistoryDialog() { |
| showDialogFragment( |
| DvrHistoryDialogFragment.DIALOG_TAG, new DvrHistoryDialogFragment(), false); |
| } |
| |
| /** Shows banner view. */ |
| public void showBanner() { |
| mTransitionManager.goToChannelBannerScene(); |
| } |
| |
| /** |
| * Pops up the KeypadChannelSwitchView with the given key input event. |
| * |
| * @param keyCode A key code of the key event. |
| */ |
| public void showKeypadChannelSwitch(int keyCode) { |
| if (mChannelTuner.areAllChannelsLoaded()) { |
| hideOverlays( |
| TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE |
| | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS |
| | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG |
| | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); |
| mTransitionManager.goToKeypadChannelSwitchScene(); |
| mKeypadChannelSwitchView.onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0); |
| } |
| } |
| |
| /** Shows select input view. */ |
| public void showSelectInputView() { |
| hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE); |
| mTransitionManager.goToSelectInputScene(); |
| } |
| |
| /** Initializes animators if animators are not initialized yet. */ |
| public void initAnimatorIfNeeded() { |
| mTransitionManager.initIfNeeded(); |
| } |
| |
| /** It is called when a SafeDismissDialogFragment is destroyed. */ |
| public void onDialogDestroyed() { |
| mCurrentDialog = null; |
| PendingDialogAction action = mPendingDialogActionQueue.poll(); |
| if (action == null) { |
| onOverlayClosed(OVERLAY_TYPE_DIALOG); |
| } else { |
| action.run(); |
| } |
| } |
| |
| /** Shows the program guide. */ |
| public void showProgramGuide() { |
| mProgramGuide.show( |
| () -> hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE)); |
| } |
| |
| /** |
| * Shows/hides the program guide according to it's hidden or shown now. |
| * |
| * @return {@code true} if program guide is going to be shown, otherwise {@code false}. |
| */ |
| public boolean toggleProgramGuide() { |
| if (mProgramGuide.isActive()) { |
| mProgramGuide.onBackPressed(); |
| return false; |
| } else { |
| showProgramGuide(); |
| return true; |
| } |
| } |
| |
| /** Sets blocking content rating of the currently playing TV channel. */ |
| public void setBlockingContentRating(TvContentRating rating) { |
| if (!mMainActivity.isChannelChangeKeyDownReceived()) { |
| mChannelBannerView.setBlockingContentRating(rating); |
| updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); |
| } |
| } |
| |
| public boolean isOverlayOpened() { |
| return mOpenedOverlays != OVERLAY_TYPE_NONE; |
| } |
| |
| /** Hides all the opened overlays according to the flags. */ |
| // TODO: Add test for this method. |
| public void hideOverlays(@HideOverlayFlag int flags) { |
| if (mMainActivity.needToKeepSetupScreenWhenHidingOverlay()) { |
| flags |= FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT; |
| } |
| if ((flags & FLAG_HIDE_OVERLAYS_KEEP_DIALOG) != 0) { |
| // Keeps the dialog. |
| } else { |
| if (mCurrentDialog != null) { |
| if (mCurrentDialog instanceof PinDialogFragment) { |
| // We don't want any OnPinCheckedListener is triggered to prevent any possible |
| // side effects. Dismisses the dialog silently. |
| ((PinDialogFragment) mCurrentDialog).dismissSilently(); |
| } else { |
| mCurrentDialog.dismiss(); |
| } |
| } |
| mPendingDialogActionQueue.clear(); |
| mCurrentDialog = null; |
| } |
| boolean withAnimation = (flags & FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION) == 0; |
| |
| if ((flags & FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT) == 0) { |
| Fragment setupSourcesFragment = getSetupSourcesFragment(); |
| Fragment newSourcesFragment = getNewSourcesFragment(); |
| if (mSetupFragmentActive) { |
| if (!withAnimation && setupSourcesFragment != null) { |
| setupSourcesFragment.setReturnTransition(null); |
| setupSourcesFragment.setExitTransition(null); |
| } |
| closeSetupFragment(true); |
| } |
| if (mNewSourcesFragmentActive) { |
| if (!withAnimation && newSourcesFragment != null) { |
| newSourcesFragment.setReturnTransition(null); |
| newSourcesFragment.setExitTransition(null); |
| } |
| closeNewSourcesFragment(true); |
| } |
| } |
| |
| if ((flags & FLAG_HIDE_OVERLAYS_KEEP_MENU) != 0) { |
| // Keeps the menu. |
| } else { |
| mMenu.hide(withAnimation); |
| } |
| if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SCENE) != 0) { |
| // Keeps the current scene. |
| } else { |
| mTransitionManager.goToEmptyScene(withAnimation); |
| } |
| if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS) != 0) { |
| // Keeps side panels. |
| } else if (mSideFragmentManager.isActive()) { |
| if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY) != 0) { |
| mSideFragmentManager.hideSidePanel(withAnimation); |
| } else { |
| mSideFragmentManager.hideAll(withAnimation); |
| } |
| } |
| if ((flags & FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE) != 0) { |
| // Keep the program guide. |
| } else { |
| mProgramGuide.hide(); |
| } |
| } |
| |
| @Override |
| public void onAccessibilityStateChanged(boolean enabled) { |
| // Propagate this to all elements that need it |
| mChannelBannerView.onAccessibilityStateChanged(enabled); |
| mProgramGuide.onAccessibilityStateChanged(enabled); |
| mSideFragmentManager.onAccessibilityStateChanged(enabled); |
| } |
| |
| /** |
| * Returns true, if a main view needs to hide informational text. Specifically, when overlay UIs |
| * except banner is shown, the informational text needs to be hidden for clean UI. |
| */ |
| public boolean needHideTextOnMainView() { |
| return mSideFragmentManager.isActive() |
| || getMenu().isActive() |
| || mTransitionManager.isKeypadChannelSwitchActive() |
| || mTransitionManager.isSelectInputActive() |
| || mSetupFragmentActive |
| || mNewSourcesFragmentActive; |
| } |
| |
| /** Updates and shows channel banner if it's needed. */ |
| public void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) { |
| if (DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")"); |
| if (mMainActivity.isChannelChangeKeyDownReceived() |
| && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE |
| && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) { |
| // Tuning is still ongoing, no need to update banner for other reasons |
| return; |
| } |
| if (!mChannelTuner.isCurrentChannelPassthrough()) { |
| int lockType = ChannelBannerView.LOCK_NONE; |
| if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) { |
| if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled() |
| && mMainActivity.getCurrentChannel().isLocked()) { |
| lockType = ChannelBannerView.LOCK_CHANNEL_INFO; |
| } else { |
| // Do not show detailed program information while fast-tuning. |
| lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; |
| } |
| } else if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE) { |
| if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled()) { |
| if (mMainActivity.getCurrentChannel().isLocked()) { |
| lockType = ChannelBannerView.LOCK_CHANNEL_INFO; |
| } else { |
| // If parental control is turned on, |
| // assumes that program is locked by default and waits for onContentAllowed. |
| lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; |
| } |
| } |
| } else if (mTvView.isScreenBlocked()) { |
| lockType = ChannelBannerView.LOCK_CHANNEL_INFO; |
| } else if (mTvView.isContentBlocked() |
| || (mMainActivity.getParentalControlSettings().isParentalControlsEnabled() |
| && !mTvView.isVideoOrAudioAvailable())) { |
| // If the parental control is enabled, do not show the program detail until the |
| // video becomes available. |
| lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; |
| } |
| // If lock type is not changed, we don't need to update channel banner by parental |
| // control. |
| int previousLockType = mChannelBannerView.setLockType(lockType); |
| if (previousLockType == lockType |
| && reason == UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK) { |
| return; |
| } else if (reason == UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO) { |
| mChannelBannerView.updateStreamInfo(mTvView); |
| // If parental control is enabled, we shows program description when the video is |
| // available, instead of tuning. Therefore we need to check it here if the program |
| // description is previously hidden by parental control. |
| if (previousLockType == ChannelBannerView.LOCK_PROGRAM_DETAIL |
| && lockType != ChannelBannerView.LOCK_PROGRAM_DETAIL) { |
| mChannelBannerView.updateViews(false); |
| } |
| } else if (CommonFeatures.TUNER_SIGNAL_STRENGTH.isEnabled(mMainActivity) |
| && reason == UPDATE_CHANNEL_BANNER_REASON_UPDATE_SIGNAL_STRENGTH) { |
| mChannelBannerView.updateChannelSignalStrengthView( |
| mTvView.getChannelSignalStrength()); |
| } else { |
| mChannelBannerView.updateViews( |
| reason == UPDATE_CHANNEL_BANNER_REASON_TUNE |
| || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); |
| } |
| } |
| boolean needToShowBanner = |
| (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW |
| || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE |
| || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); |
| if (needToShowBanner |
| && !mMainActivity.willShowOverlayUiWhenResume() |
| && getCurrentDialog() == null |
| && !isSetupFragmentActive() |
| && !isNewSourcesFragmentActive()) { |
| if (mChannelTuner.getCurrentChannel() == null) { |
| mChannelBannerHiddenBySideFragment = false; |
| } else if (getSideFragmentManager().isActive()) { |
| mChannelBannerHiddenBySideFragment = true; |
| } else { |
| mChannelBannerHiddenBySideFragment = false; |
| showBanner(); |
| } |
| } |
| } |
| |
| @TvOverlayType |
| private int convertSceneToOverlayType(@SceneType int sceneType) { |
| switch (sceneType) { |
| case TvTransitionManager.SCENE_TYPE_CHANNEL_BANNER: |
| return OVERLAY_TYPE_SCENE_CHANNEL_BANNER; |
| case TvTransitionManager.SCENE_TYPE_INPUT_BANNER: |
| return OVERLAY_TYPE_SCENE_INPUT_BANNER; |
| case TvTransitionManager.SCENE_TYPE_KEYPAD_CHANNEL_SWITCH: |
| return OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH; |
| case TvTransitionManager.SCENE_TYPE_SELECT_INPUT: |
| return OVERLAY_TYPE_SCENE_SELECT_INPUT; |
| case TvTransitionManager.SCENE_TYPE_EMPTY: |
| default: |
| return OVERLAY_TYPE_NONE; |
| } |
| } |
| |
| private void onOverlayOpened(@TvOverlayType int overlayType) { |
| if (DEBUG) Log.d(TAG, "Overlay opened: " + toBinaryString(overlayType)); |
| mOpenedOverlays |= overlayType; |
| if (DEBUG) Log.d(TAG, "Opened overlays: " + toBinaryString(mOpenedOverlays)); |
| mHandler.removeMessages(MSG_OVERLAY_CLOSED); |
| mMainActivity.updateKeyInputFocus(); |
| } |
| |
| private void onOverlayClosed(@TvOverlayType int overlayType) { |
| if (DEBUG) Log.d(TAG, "Overlay closed: " + toBinaryString(overlayType)); |
| mOpenedOverlays &= ~overlayType; |
| if (DEBUG) Log.d(TAG, "Opened overlays: " + toBinaryString(mOpenedOverlays)); |
| mHandler.removeMessages(MSG_OVERLAY_CLOSED); |
| mMainActivity.updateKeyInputFocus(); |
| // Show the main menu again if there are no pop-ups or banners only. |
| // The main menu should not be shown when the activity is in paused state. |
| boolean menuAboutToShow = false; |
| if (canExecuteCloseAction()) { |
| menuAboutToShow = mMainActivity.getTimeShiftManager().isPaused(); |
| mHandler.sendEmptyMessage(MSG_OVERLAY_CLOSED); |
| } |
| // Don't set screen name to main if the overlay closing is a banner |
| // or if a non banner overlay is still open |
| // or if we just opened the menu |
| if (overlayType != OVERLAY_TYPE_SCENE_CHANNEL_BANNER |
| && overlayType != OVERLAY_TYPE_SCENE_INPUT_BANNER |
| && isOnlyBannerOrNoneOpened() |
| && !menuAboutToShow) { |
| mTracker.sendScreenView(MainActivity.SCREEN_NAME); |
| } |
| } |
| |
| /** |
| * Shows the channel banner if it was hidden from the side fragment. |
| * |
| * <p>When the side fragment is visible, showing the channel banner should be put off until the |
| * side fragment is closed even though the channel changes. |
| */ |
| private void showChannelBannerIfHiddenBySideFragment() { |
| if (mChannelBannerHiddenBySideFragment) { |
| updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); |
| } |
| } |
| |
| private String toBinaryString(int value) { |
| return String.format("0b%" + NUM_OVERLAY_TYPES + "s", Integer.toBinaryString(value)) |
| .replace(' ', '0'); |
| } |
| |
| private boolean canExecuteCloseAction() { |
| return mMainActivity.isActivityResumed() && isOnlyBannerOrNoneOpened(); |
| } |
| |
| private boolean isOnlyBannerOrNoneOpened() { |
| return (mOpenedOverlays |
| & ~OVERLAY_TYPE_SCENE_CHANNEL_BANNER |
| & ~OVERLAY_TYPE_SCENE_INPUT_BANNER) |
| == 0; |
| } |
| |
| /** Runs a given {@code action} after all the overlays are closed. */ |
| public void runAfterOverlaysAreClosed(Runnable action) { |
| if (canExecuteCloseAction()) { |
| action.run(); |
| } else { |
| mPendingActions.add(action); |
| } |
| } |
| |
| /** Handles the onUserInteraction event of the {@link MainActivity}. */ |
| public void onUserInteraction() { |
| if (mSideFragmentManager.isActive()) { |
| mSideFragmentManager.scheduleHideAll(); |
| } else if (mMenu.isActive()) { |
| mMenu.scheduleHide(); |
| } else if (mProgramGuide.isActive()) { |
| mProgramGuide.scheduleHide(); |
| } |
| } |
| |
| /** Handles the onKeyDown event of the {@link MainActivity}. */ |
| @KeyHandlerResultType |
| public int onKeyDown(int keyCode, KeyEvent event) { |
| if (mCurrentDialog != null) { |
| // Consumes the keys while a Dialog is creating. |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| // Handle media key here because it is related to the menu. |
| if (isMediaStartKey(keyCode)) { |
| // Consumes the keys which may trigger system's default music player. |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| if (mMenu.isActive() |
| || mSideFragmentManager.isActive() |
| || mProgramGuide.isActive() |
| || mSetupFragmentActive |
| || mNewSourcesFragmentActive) { |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; |
| } |
| if (mTransitionManager.isKeypadChannelSwitchActive()) { |
| return mKeypadChannelSwitchView.onKeyDown(keyCode, event) |
| ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED |
| : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; |
| } |
| if (mTransitionManager.isSelectInputActive()) { |
| return mSelectInputView.onKeyDown(keyCode, event) |
| ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED |
| : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; |
| } |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH; |
| } |
| |
| /** Handles the onKeyUp event of the {@link MainActivity}. */ |
| @KeyHandlerResultType |
| public int onKeyUp(int keyCode, KeyEvent event) { |
| // Handle media key here because it is related to the menu. |
| if (isMediaStartKey(keyCode)) { |
| // The media key should not be passed up to the system in any cases. |
| if (mCurrentDialog != null |
| || mProgramGuide.isActive() |
| || mSideFragmentManager.isActive() |
| || mSearchFragment.isVisible() |
| || mTransitionManager.isKeypadChannelSwitchActive() |
| || mTransitionManager.isSelectInputActive() |
| || mSetupFragmentActive |
| || mNewSourcesFragmentActive) { |
| // Do not handle media key when any pop-ups which can handle keys are active. |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| if (mTvView.isScreenBlocked()) { |
| // Do not handle media key when screen is blocked. |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager(); |
| if (!timeShiftManager.isAvailable()) { |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_MEDIA_PLAY: |
| timeShiftManager.play(); |
| showMenu(Menu.REASON_PLAY_CONTROLS_PLAY); |
| break; |
| case KeyEvent.KEYCODE_MEDIA_STOP: |
| case KeyEvent.KEYCODE_MEDIA_PAUSE: |
| timeShiftManager.pause(); |
| showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE); |
| break; |
| case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: |
| timeShiftManager.togglePlayPause(); |
| showMenu(Menu.REASON_PLAY_CONTROLS_PLAY_PAUSE); |
| break; |
| case KeyEvent.KEYCODE_MEDIA_REWIND: |
| timeShiftManager.rewind(); |
| showMenu(Menu.REASON_PLAY_CONTROLS_REWIND); |
| break; |
| case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: |
| timeShiftManager.fastForward(); |
| showMenu(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD); |
| break; |
| case KeyEvent.KEYCODE_MEDIA_PREVIOUS: |
| case KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD: |
| timeShiftManager.jumpToPrevious(); |
| showMenu(Menu.REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS); |
| break; |
| case KeyEvent.KEYCODE_MEDIA_NEXT: |
| case KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD: |
| timeShiftManager.jumpToNext(); |
| showMenu(Menu.REASON_PLAY_CONTROLS_JUMP_TO_NEXT); |
| break; |
| default: |
| // Does nothing. |
| break; |
| } |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| if (keyCode == KeyEvent.KEYCODE_I || keyCode == KeyEvent.KEYCODE_TV_INPUT) { |
| if (mTransitionManager.isSelectInputActive()) { |
| mSelectInputView.onKeyUp(keyCode, event); |
| } else { |
| showSelectInputView(); |
| } |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| if (mCurrentDialog != null) { |
| // Consumes the keys while a Dialog is showing. |
| // This can be happen while a Dialog isn't created yet. |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| if (mProgramGuide.isActive()) { |
| if (keyCode == KeyEvent.KEYCODE_BACK) { |
| mProgramGuide.onBackPressed(); |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; |
| } |
| if (mSideFragmentManager.isActive()) { |
| if (keyCode == KeyEvent.KEYCODE_BACK |
| || mSideFragmentManager.isHideKeyForCurrentPanel(keyCode)) { |
| mSideFragmentManager.popSideFragment(); |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; |
| } |
| if (mMenu.isActive() || mTransitionManager.isSceneActive()) { |
| if (keyCode == KeyEvent.KEYCODE_BACK) { |
| TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager(); |
| if (timeShiftManager.isPaused()) { |
| timeShiftManager.play(); |
| } |
| hideOverlays( |
| TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS |
| | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG |
| | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| if (mMenu.isActive()) { |
| if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) { |
| showKeypadChannelSwitch(keyCode); |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; |
| } |
| } |
| if (mTransitionManager.isKeypadChannelSwitchActive()) { |
| if (keyCode == KeyEvent.KEYCODE_BACK) { |
| mTransitionManager.goToEmptyScene(true); |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| return mKeypadChannelSwitchView.onKeyUp(keyCode, event) |
| ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED |
| : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; |
| } |
| if (mTransitionManager.isSelectInputActive()) { |
| if (keyCode == KeyEvent.KEYCODE_BACK) { |
| mTransitionManager.goToEmptyScene(true); |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| return mSelectInputView.onKeyUp(keyCode, event) |
| ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED |
| : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; |
| } |
| if (mSetupFragmentActive) { |
| if (keyCode == KeyEvent.KEYCODE_BACK) { |
| closeSetupFragment(true); |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; |
| } |
| if (mNewSourcesFragmentActive) { |
| if (keyCode == KeyEvent.KEYCODE_BACK) { |
| closeNewSourcesFragment(true); |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; |
| } |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; |
| } |
| return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH; |
| } |
| |
| /** Checks whether the given {@code keyCode} can start the system's music app or not. */ |
| private static boolean isMediaStartKey(int keyCode) { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: |
| case KeyEvent.KEYCODE_MEDIA_PLAY: |
| case KeyEvent.KEYCODE_MEDIA_PAUSE: |
| case KeyEvent.KEYCODE_MEDIA_NEXT: |
| case KeyEvent.KEYCODE_MEDIA_PREVIOUS: |
| case KeyEvent.KEYCODE_MEDIA_REWIND: |
| case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: |
| case KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD: |
| case KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD: |
| case KeyEvent.KEYCODE_MEDIA_STOP: |
| return true; |
| } |
| return false; |
| } |
| |
| private static class TvOverlayHandler extends WeakHandler<TvOverlayManager> { |
| TvOverlayHandler(TvOverlayManager ref) { |
| super(ref); |
| } |
| |
| @Override |
| public void handleMessage(Message msg, @NonNull TvOverlayManager tvOverlayManager) { |
| switch (msg.what) { |
| case MSG_OVERLAY_CLOSED: |
| if (!tvOverlayManager.canExecuteCloseAction()) { |
| return; |
| } |
| if (tvOverlayManager.showMenuWithTimeShiftPauseIfNeeded()) { |
| return; |
| } |
| if (!tvOverlayManager.mPendingActions.isEmpty()) { |
| Runnable action = tvOverlayManager.mPendingActions.get(0); |
| tvOverlayManager.mPendingActions.remove(action); |
| action.run(); |
| } |
| break; |
| } |
| } |
| } |
| |
| private class PendingDialogAction { |
| private final String mTag; |
| private final SafeDismissDialogFragment mDialog; |
| private final boolean mKeepSidePanelHistory; |
| private final boolean mKeepProgramGuide; |
| |
| PendingDialogAction( |
| String tag, |
| SafeDismissDialogFragment dialog, |
| boolean keepSidePanelHistory, |
| boolean keepProgramGuide) { |
| mTag = tag; |
| mDialog = dialog; |
| mKeepSidePanelHistory = keepSidePanelHistory; |
| mKeepProgramGuide = keepProgramGuide; |
| } |
| |
| void run() { |
| showDialogFragment(mTag, mDialog, mKeepSidePanelHistory, mKeepProgramGuide); |
| } |
| } |
| } |