Home | History | Annotate | Download | only in tv
      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;
     18 
     19 import android.app.Activity;
     20 import android.content.ActivityNotFoundException;
     21 import android.content.BroadcastReceiver;
     22 import android.content.ComponentName;
     23 import android.content.ContentUris;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.IntentFilter;
     27 import android.content.pm.PackageManager;
     28 import android.database.Cursor;
     29 import android.graphics.Bitmap;
     30 import android.graphics.BitmapFactory;
     31 import android.graphics.PixelFormat;
     32 import android.graphics.Point;
     33 import android.hardware.display.DisplayManager;
     34 import android.media.AudioManager;
     35 import android.media.MediaMetadata;
     36 import android.media.session.MediaSession;
     37 import android.media.session.PlaybackState;
     38 import android.media.tv.TvContentRating;
     39 import android.media.tv.TvContract;
     40 import android.media.tv.TvContract.Channels;
     41 import android.media.tv.TvInputInfo;
     42 import android.media.tv.TvInputManager;
     43 import android.media.tv.TvInputManager.TvInputCallback;
     44 import android.media.tv.TvTrackInfo;
     45 import android.media.tv.TvView.OnUnhandledInputEventListener;
     46 import android.net.Uri;
     47 import android.os.Build;
     48 import android.os.Bundle;
     49 import android.os.Handler;
     50 import android.os.Message;
     51 import android.os.PowerManager;
     52 import android.provider.Settings;
     53 import android.support.annotation.IntDef;
     54 import android.support.annotation.NonNull;
     55 import android.support.annotation.Nullable;
     56 import android.support.v4.os.BuildCompat;
     57 import android.text.TextUtils;
     58 import android.util.Log;
     59 import android.view.Display;
     60 import android.view.Gravity;
     61 import android.view.InputEvent;
     62 import android.view.KeyEvent;
     63 import android.view.View;
     64 import android.view.ViewGroup;
     65 import android.view.Window;
     66 import android.view.WindowManager;
     67 import android.view.accessibility.AccessibilityEvent;
     68 import android.view.accessibility.AccessibilityManager;
     69 import android.widget.FrameLayout;
     70 import android.widget.Toast;
     71 
     72 import com.android.tv.analytics.DurationTimer;
     73 import com.android.tv.analytics.SendChannelStatusRunnable;
     74 import com.android.tv.analytics.SendConfigInfoRunnable;
     75 import com.android.tv.analytics.Tracker;
     76 import com.android.tv.common.BuildConfig;
     77 import com.android.tv.common.MemoryManageable;
     78 import com.android.tv.common.SoftPreconditions;
     79 import com.android.tv.common.TvCommonUtils;
     80 import com.android.tv.common.TvContentRatingCache;
     81 import com.android.tv.common.WeakHandler;
     82 import com.android.tv.common.feature.CommonFeatures;
     83 import com.android.tv.common.recording.RecordedProgram;
     84 import com.android.tv.data.Channel;
     85 import com.android.tv.data.ChannelDataManager;
     86 import com.android.tv.data.OnCurrentProgramUpdatedListener;
     87 import com.android.tv.data.Program;
     88 import com.android.tv.data.ProgramDataManager;
     89 import com.android.tv.data.StreamInfo;
     90 import com.android.tv.data.WatchedHistoryManager;
     91 import com.android.tv.dialog.PinDialogFragment;
     92 import com.android.tv.dialog.SafeDismissDialogFragment;
     93 import com.android.tv.dvr.DvrDataManager;
     94 import com.android.tv.dvr.DvrManager;
     95 import com.android.tv.dvr.DvrPlayActivity;
     96 import com.android.tv.dvr.ScheduledRecording;
     97 import com.android.tv.menu.Menu;
     98 import com.android.tv.onboarding.OnboardingActivity;
     99 import com.android.tv.parental.ContentRatingsManager;
    100 import com.android.tv.parental.ParentalControlSettings;
    101 import com.android.tv.receiver.AudioCapabilitiesReceiver;
    102 import com.android.tv.recommendation.NotificationService;
    103 import com.android.tv.search.ProgramGuideSearchFragment;
    104 import com.android.tv.ui.AppLayerTvView;
    105 import com.android.tv.ui.ChannelBannerView;
    106 import com.android.tv.ui.InputBannerView;
    107 import com.android.tv.ui.KeypadChannelSwitchView;
    108 import com.android.tv.ui.OverlayRootView;
    109 import com.android.tv.ui.SelectInputView;
    110 import com.android.tv.ui.SelectInputView.OnInputSelectedCallback;
    111 import com.android.tv.ui.TunableTvView;
    112 import com.android.tv.ui.TunableTvView.OnTuneListener;
    113 import com.android.tv.ui.TvOverlayManager;
    114 import com.android.tv.ui.TvViewUiManager;
    115 import com.android.tv.ui.sidepanel.ClosedCaptionFragment;
    116 import com.android.tv.ui.sidepanel.CustomizeChannelListFragment;
    117 import com.android.tv.ui.sidepanel.DebugOptionFragment;
    118 import com.android.tv.ui.sidepanel.DisplayModeFragment;
    119 import com.android.tv.ui.sidepanel.MultiAudioFragment;
    120 import com.android.tv.ui.sidepanel.SettingsFragment;
    121 import com.android.tv.ui.sidepanel.SideFragment;
    122 import com.android.tv.util.CaptionSettings;
    123 import com.android.tv.util.ImageCache;
    124 import com.android.tv.util.ImageLoader;
    125 import com.android.tv.util.OnboardingUtils;
    126 import com.android.tv.util.PermissionUtils;
    127 import com.android.tv.util.PipInputManager;
    128 import com.android.tv.util.PipInputManager.PipInput;
    129 import com.android.tv.util.RecurringRunner;
    130 import com.android.tv.util.SearchManagerHelper;
    131 import com.android.tv.util.SetupUtils;
    132 import com.android.tv.util.SystemProperties;
    133 import com.android.tv.util.TvInputManagerHelper;
    134 import com.android.tv.util.TvSettings;
    135 import com.android.tv.util.TvSettings.PipSound;
    136 import com.android.usbtuner.UsbTunerPreferences;
    137 import com.android.usbtuner.setup.TunerSetupActivity;
    138 import com.android.usbtuner.tvinput.UsbTunerTvInputService;
    139 import com.android.tv.util.TvTrackInfoUtils;
    140 import com.android.tv.util.Utils;
    141 
    142 import java.lang.annotation.Retention;
    143 import java.lang.annotation.RetentionPolicy;
    144 import java.util.ArrayDeque;
    145 import java.util.ArrayList;
    146 import java.util.HashSet;
    147 import java.util.List;
    148 import java.util.Objects;
    149 import java.util.concurrent.TimeUnit;
    150 
    151 /**
    152  * The main activity for the Live TV app.
    153  */
    154 public class MainActivity extends Activity implements AudioManager.OnAudioFocusChangeListener {
    155     private static final String TAG = "MainActivity";
    156     private static final boolean DEBUG = false;
    157 
    158     @Retention(RetentionPolicy.SOURCE)
    159     @IntDef({KEY_EVENT_HANDLER_RESULT_PASSTHROUGH, KEY_EVENT_HANDLER_RESULT_NOT_HANDLED,
    160         KEY_EVENT_HANDLER_RESULT_HANDLED, KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY})
    161     public @interface KeyHandlerResultType {}
    162     public static final int KEY_EVENT_HANDLER_RESULT_PASSTHROUGH = 0;
    163     public static final int KEY_EVENT_HANDLER_RESULT_NOT_HANDLED = 1;
    164     public static final int KEY_EVENT_HANDLER_RESULT_HANDLED = 2;
    165     public static final int KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY = 3;
    166 
    167     private static final boolean USE_BACK_KEY_LONG_PRESS = false;
    168 
    169     private static final float AUDIO_MAX_VOLUME = 1.0f;
    170     private static final float AUDIO_MIN_VOLUME = 0.0f;
    171     private static final float AUDIO_DUCKING_VOLUME = 0.3f;
    172     private static final float FRAME_RATE_FOR_FILM = 23.976f;
    173     private static final float FRAME_RATE_EPSILON = 0.1f;
    174 
    175     private static final float MEDIA_SESSION_STOPPED_SPEED = 0.0f;
    176     private static final float MEDIA_SESSION_PLAYING_SPEED = 1.0f;
    177 
    178 
    179     private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1;
    180     private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
    181 
    182     private static final String USB_TV_TUNER_INPUT_ID =
    183             "com.android.tv/com.android.usbtuner.tvinput.UsbTunerTvInputService";
    184     private static final String DVR_TEST_INPUT_ID = USB_TV_TUNER_INPUT_ID;
    185 
    186     // Tracker screen names.
    187     public static final String SCREEN_NAME = "Main";
    188     private static final String SCREEN_BEHIND_NAME = "Behind";
    189 
    190     private static final float REFRESH_RATE_EPSILON = 0.01f;
    191     private static final HashSet<Integer> BLACKLIST_KEYCODE_TO_TIS;
    192     // These keys won't be passed to TIS in addition to gamepad buttons.
    193     static {
    194         BLACKLIST_KEYCODE_TO_TIS = new HashSet<>();
    195         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_TV_INPUT);
    196         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MENU);
    197         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_UP);
    198         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_DOWN);
    199         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_UP);
    200         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_DOWN);
    201         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_MUTE);
    202         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MUTE);
    203         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_SEARCH);
    204     }
    205 
    206     private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
    207     private static final int REQUEST_CODE_START_SYSTEM_CAPTIONING_SETTINGS = 2;
    208 
    209     private static final String KEY_INIT_CHANNEL_ID = "com.android.tv.init_channel_id";
    210 
    211     private static final String MEDIA_SESSION_TAG = "com.android.tv.mediasession";
    212 
    213     // Change channels with key long press.
    214     private static final int CHANNEL_CHANGE_NORMAL_SPEED_DURATION_MS = 3000;
    215     private static final int CHANNEL_CHANGE_DELAY_MS_IN_MAX_SPEED = 50;
    216     private static final int CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED = 200;
    217     private static final int CHANNEL_CHANGE_INITIAL_DELAY_MILLIS = 500;
    218     private static final int FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS = 500;
    219 
    220     private static final int MSG_CHANNEL_DOWN_PRESSED = 1000;
    221     private static final int MSG_CHANNEL_UP_PRESSED = 1001;
    222     private static final int MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE = 1002;
    223 
    224     @Retention(RetentionPolicy.SOURCE)
    225     @IntDef({UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW, UPDATE_CHANNEL_BANNER_REASON_TUNE,
    226         UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST, UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO,
    227         UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK})
    228     private @interface ChannelBannerUpdateReason {}
    229     private static final int UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW = 1;
    230     private static final int UPDATE_CHANNEL_BANNER_REASON_TUNE = 2;
    231     private static final int UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST = 3;
    232     private static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO = 4;
    233     private static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5;
    234 
    235     private static final int TVVIEW_SET_MAIN_TIMEOUT_MS = 3000;
    236 
    237     // Lazy initialization.
    238     // Delay 1 second in order not to interrupt the first tune.
    239     private static final long LAZY_INITIALIZATION_DELAY = TimeUnit.SECONDS.toMillis(1);
    240 
    241     private AccessibilityManager mAccessibilityManager;
    242     private ChannelDataManager mChannelDataManager;
    243     private ProgramDataManager mProgramDataManager;
    244     private TvInputManagerHelper mTvInputManagerHelper;
    245     private ChannelTuner mChannelTuner;
    246     private PipInputManager mPipInputManager;
    247     private final TvOptionsManager mTvOptionsManager = new TvOptionsManager(this);
    248     private TvViewUiManager mTvViewUiManager;
    249     private TimeShiftManager mTimeShiftManager;
    250     private Tracker mTracker;
    251     private final DurationTimer mMainDurationTimer = new DurationTimer();
    252     private final DurationTimer mTuneDurationTimer = new DurationTimer();
    253     private DvrManager mDvrManager;
    254     private DvrDataManager mDvrDataManager;
    255 
    256     private TunableTvView mTvView;
    257     private TunableTvView mPipView;
    258     private OverlayRootView mOverlayRootView;
    259     private Bundle mTuneParams;
    260     private boolean mChannelBannerHiddenBySideFragment;
    261     // TODO: Move the scene views into TvTransitionManager or TvOverlayManager.
    262     private ChannelBannerView mChannelBannerView;
    263     private KeypadChannelSwitchView mKeypadChannelSwitchView;
    264     @Nullable
    265     private Uri mInitChannelUri;
    266     @Nullable
    267     private String mParentInputIdWhenScreenOff;
    268     private boolean mScreenOffIntentReceived;
    269     private boolean mShowProgramGuide;
    270     private boolean mShowSelectInputView;
    271     private TvInputInfo mInputToSetUp;
    272     private final List<MemoryManageable> mMemoryManageables = new ArrayList<>();
    273     private MediaSession mMediaSession;
    274     private int mNowPlayingCardWidth;
    275     private int mNowPlayingCardHeight;
    276     private final MyOnTuneListener mOnTuneListener = new MyOnTuneListener();
    277 
    278     private String mInputIdUnderSetup;
    279     private boolean mIsSetupActivityCalledByPopup;
    280     private AudioManager mAudioManager;
    281     private int mAudioFocusStatus;
    282     private boolean mTunePending;
    283     private boolean mPipEnabled;
    284     private Channel mPipChannel;
    285     private boolean mPipSwap;
    286     @PipSound private int mPipSound = TvSettings.PIP_SOUND_MAIN; // Default
    287     private boolean mDebugNonFullSizeScreen;
    288     private boolean mActivityResumed;
    289     private boolean mActivityStarted;
    290     private boolean mShouldTuneToTunerChannel;
    291     private boolean mUseKeycodeBlacklist;
    292     private boolean mShowLockedChannelsTemporarily;
    293     private boolean mBackKeyPressed;
    294     private boolean mNeedShowBackKeyGuide;
    295     private boolean mVisibleBehind;
    296     private boolean mAc3PassthroughSupported;
    297     private boolean mShowNewSourcesFragment = true;
    298     private Uri mRecordingUri;
    299     private String mUsbTunerInputId;
    300     private boolean mOtherActivityLaunched;
    301 
    302     private boolean mIsFilmModeSet;
    303     private float mDefaultRefreshRate;
    304 
    305     private TvOverlayManager mOverlayManager;
    306 
    307     // mIsCurrentChannelUnblockedByUser and mWasChannelUnblockedBeforeShrunkenByUser are used for
    308     // keeping the channel unblocking status while TV view is shrunken.
    309     private boolean mIsCurrentChannelUnblockedByUser;
    310     private boolean mWasChannelUnblockedBeforeShrunkenByUser;
    311     private Channel mChannelBeforeShrunkenTvView;
    312     private Channel mPipChannelBeforeShrunkenTvView;
    313     private boolean mIsCompletingShrunkenTvView;
    314 
    315     // TODO: Need to consider the case that TIS explicitly request PIN code while TV view is
    316     // shrunken.
    317     private TvContentRating mLastAllowedRatingForCurrentChannel;
    318     private TvContentRating mAllowedRatingBeforeShrunken;
    319 
    320     private CaptionSettings mCaptionSettings;
    321     // Lazy initialization
    322     private boolean mLazyInitialized;
    323 
    324     private static final int MAX_RECENT_CHANNELS = 5;
    325     private final ArrayDeque<Long> mRecentChannels = new ArrayDeque<>(MAX_RECENT_CHANNELS);
    326 
    327     private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
    328     private RecurringRunner mSendConfigInfoRecurringRunner;
    329     private RecurringRunner mChannelStatusRecurringRunner;
    330 
    331     // A caller which started this activity. (e.g. TvSearch)
    332     private String mSource;
    333 
    334     private final Handler mHandler = new MainActivityHandler(this);
    335 
    336     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    337         @Override
    338         public void onReceive(Context context, Intent intent) {
    339             if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
    340                 if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF");
    341                 // We need to stop TvView, when the screen is turned off. If not and TIS uses
    342                 // MediaPlayer, a device may not go to the sleep mode and audio can be heard,
    343                 // because MediaPlayer keeps playing media by its wake lock.
    344                 mScreenOffIntentReceived = true;
    345                 markCurrentChannelDuringScreenOff();
    346                 stopAll(true);
    347             } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
    348                 if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON");
    349                 if (!mActivityResumed && mVisibleBehind) {
    350                     // ACTION_SCREEN_ON is usually called after onResume. But, if media is played
    351                     // under launcher with requestVisibleBehind(true), onResume will not be called.
    352                     // In this case, we need to resume TvView and PipView explicitly.
    353                     resumeTvIfNeeded();
    354                     resumePipIfNeeded();
    355                 }
    356             } else if (intent.getAction().equals(
    357                     TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED)) {
    358                 if (DEBUG) Log.d(TAG, "Received parental control settings change");
    359                 checkChannelLockNeeded(mTvView);
    360                 checkChannelLockNeeded(mPipView);
    361                 applyParentalControlSettings();
    362             }
    363         }
    364     };
    365 
    366     private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener =
    367             new OnCurrentProgramUpdatedListener() {
    368         @Override
    369         public void onCurrentProgramUpdated(long channelId, Program program) {
    370             // Do not update channel banner by this notification
    371             // when the time shifting is available.
    372             if (mTimeShiftManager.isAvailable()) {
    373                 return;
    374             }
    375             Channel channel = mTvView.getCurrentChannel();
    376             if (channel != null && channel.getId() == channelId) {
    377                 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
    378                 updateMediaSession();
    379             }
    380         }
    381     };
    382 
    383     private final ChannelTuner.Listener mChannelTunerListener =
    384             new ChannelTuner.Listener() {
    385                 @Override
    386                 public void onLoadFinished() {
    387                     SetupUtils.getInstance(MainActivity.this).markNewChannelsBrowsable();
    388                     if (mActivityResumed) {
    389                         resumeTvIfNeeded();
    390                         resumePipIfNeeded();
    391                     }
    392                     mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList());
    393                     mHandler.post(new Runnable() {
    394                         @Override
    395                         public void run() {
    396                             mOverlayManager.getMenu().setChannelTuner(mChannelTuner);
    397                         }
    398                     });
    399                 }
    400 
    401                 @Override
    402                 public void onBrowsableChannelListChanged() {
    403                     mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList());
    404                 }
    405 
    406                 @Override
    407                 public void onCurrentChannelUnavailable(Channel channel) {
    408                     // TODO: handle the case that a channel is suddenly removed from DB.
    409                 }
    410 
    411                 @Override
    412                 public void onChannelChanged(Channel previousChannel, Channel currentChannel) {
    413                 }
    414             };
    415 
    416     private final Runnable mRestoreMainViewRunnable =
    417             new Runnable() {
    418                 @Override
    419                 public void run() {
    420                     restoreMainTvView();
    421                 }
    422             };
    423     private ProgramGuideSearchFragment mSearchFragment;
    424 
    425     private TvInputCallback mTvInputCallback = new TvInputCallback() {
    426         @Override
    427         public void onInputAdded(String inputId) {
    428             if (mUsbTunerInputId.equals(inputId)
    429                     && UsbTunerPreferences.shouldShowSetupActivity(MainActivity.this)) {
    430                 Intent intent = TunerSetupActivity.createSetupActivity(MainActivity.this);
    431                 startActivity(intent);
    432                 UsbTunerPreferences.setShouldShowSetupActivity(MainActivity.this, false);
    433                 SetupUtils.getInstance(MainActivity.this).markAsKnownInput(mUsbTunerInputId);
    434             }
    435         }
    436     };
    437 
    438     private void applyParentalControlSettings() {
    439         boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings()
    440                 .isParentalControlsEnabled();
    441         mTvView.onParentalControlChanged(parentalControlEnabled);
    442         mPipView.onParentalControlChanged(parentalControlEnabled);
    443     }
    444 
    445     @Override
    446     protected void onCreate(Bundle savedInstanceState) {
    447         if (DEBUG) Log.d(TAG,"onCreate()");
    448         super.onCreate(savedInstanceState);
    449         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
    450                 && !PermissionUtils.hasAccessAllEpg(this)) {
    451             Toast.makeText(this, R.string.msg_not_supported_device, Toast.LENGTH_LONG).show();
    452             finish();
    453             return;
    454         }
    455         boolean skipToShowOnboarding = getIntent().getAction() == Intent.ACTION_VIEW
    456                 && TvContract.isChannelUriForPassthroughInput(getIntent().getData());
    457         if (Features.ONBOARDING_EXPERIENCE.isEnabled(this)
    458                 && OnboardingUtils.needToShowOnboarding(this) && !skipToShowOnboarding
    459                 && !TvCommonUtils.isRunningInTest()) {
    460             // TODO: The onboarding is turned off in test, because tests are broken by the
    461             // onboarding. We need to enable the feature for tests later.
    462             startActivity(OnboardingActivity.buildIntent(this, getIntent()));
    463             finish();
    464             return;
    465         }
    466 
    467         TvApplication tvApplication = (TvApplication) getApplication();
    468         tvApplication.getMainActivityWrapper().onMainActivityCreated(this);
    469         if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
    470             Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show();
    471         }
    472         mTracker = tvApplication.getTracker();
    473         mTvInputManagerHelper = tvApplication.getTvInputManagerHelper();
    474         mTvInputManagerHelper.addCallback(mTvInputCallback);
    475         mUsbTunerInputId = UsbTunerTvInputService.getInputId(this);
    476         mChannelDataManager = tvApplication.getChannelDataManager();
    477         mProgramDataManager = tvApplication.getProgramDataManager();
    478         mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID,
    479                 mOnCurrentProgramUpdatedListener);
    480         mProgramDataManager.setPrefetchEnabled(true);
    481         mChannelTuner = new ChannelTuner(mChannelDataManager, mTvInputManagerHelper);
    482         mChannelTuner.addListener(mChannelTunerListener);
    483         mChannelTuner.start();
    484         mPipInputManager = new PipInputManager(this, mTvInputManagerHelper, mChannelTuner);
    485         mPipInputManager.start();
    486         mMemoryManageables.add(mProgramDataManager);
    487         mMemoryManageables.add(ImageCache.getInstance());
    488         mMemoryManageables.add(TvContentRatingCache.getInstance());
    489         if (CommonFeatures.DVR.isEnabled(this) && BuildCompat.isAtLeastN()) {
    490             mDvrManager = tvApplication.getDvrManager();
    491             mDvrDataManager = tvApplication.getDvrDataManager();
    492         }
    493 
    494         DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
    495         Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
    496         Point size = new Point();
    497         display.getSize(size);
    498         int screenWidth = size.x;
    499         int screenHeight = size.y;
    500         mDefaultRefreshRate = display.getRefreshRate();
    501 
    502         mOverlayRootView = (OverlayRootView) getLayoutInflater().inflate(
    503                 R.layout.overlay_root_view, null, false);
    504         setContentView(R.layout.activity_tv);
    505         mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view);
    506         int shrunkenTvViewHeight = getResources().getDimensionPixelSize(
    507                 R.dimen.shrunken_tvview_height);
    508         mTvView.initialize((AppLayerTvView) findViewById(R.id.main_tv_view), false, screenHeight,
    509                 shrunkenTvViewHeight);
    510         mTvView.setOnUnhandledInputEventListener(new OnUnhandledInputEventListener() {
    511             @Override
    512             public boolean onUnhandledInputEvent(InputEvent event) {
    513                 if (isKeyEventBlocked()) {
    514                     return true;
    515                 }
    516                 if (event instanceof KeyEvent) {
    517                     KeyEvent keyEvent = (KeyEvent) event;
    518                     if (keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.isLongPress()) {
    519                         if (onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) {
    520                             return true;
    521                         }
    522                     }
    523                     if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
    524                         return onKeyUp(keyEvent.getKeyCode(), keyEvent);
    525                     } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
    526                         return onKeyDown(keyEvent.getKeyCode(), keyEvent);
    527                     }
    528                 }
    529                 return false;
    530             }
    531         });
    532         mTimeShiftManager = new TimeShiftManager(this, mTvView, mProgramDataManager, mTracker,
    533                 new OnCurrentProgramUpdatedListener() {
    534                     @Override
    535                     public void onCurrentProgramUpdated(long channelId, Program program) {
    536                         updateMediaSession();
    537                         switch (mTimeShiftManager.getLastActionId()) {
    538                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND:
    539                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD:
    540                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS:
    541                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT:
    542                                 updateChannelBannerAndShowIfNeeded(
    543                                         UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
    544                                 break;
    545                             default:
    546                                 updateChannelBannerAndShowIfNeeded(
    547                                         UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
    548                                 break;
    549                         }
    550                     }
    551                 });
    552 
    553         mPipView = (TunableTvView) findViewById(R.id.pip_tunable_tv_view);
    554         mPipView.initialize((AppLayerTvView) findViewById(R.id.pip_tv_view), true, screenHeight,
    555                 shrunkenTvViewHeight);
    556 
    557         if (!PermissionUtils.hasAccessWatchedHistory(this)) {
    558             WatchedHistoryManager watchedHistoryManager = new WatchedHistoryManager(
    559                     getApplicationContext());
    560             watchedHistoryManager.start();
    561             mTvView.setWatchedHistoryManager(watchedHistoryManager);
    562         }
    563         mTvViewUiManager = new TvViewUiManager(this, mTvView, mPipView,
    564                 (FrameLayout) findViewById(android.R.id.content), mTvOptionsManager);
    565 
    566         mPipView.setFixedSurfaceSize(screenWidth / 2, screenHeight / 2);
    567         mPipView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW);
    568 
    569         ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container);
    570         mChannelBannerView = (ChannelBannerView) getLayoutInflater().inflate(
    571                 R.layout.channel_banner, sceneContainer, false);
    572         mKeypadChannelSwitchView = (KeypadChannelSwitchView) getLayoutInflater().inflate(
    573                 R.layout.keypad_channel_switch, sceneContainer, false);
    574         InputBannerView inputBannerView = (InputBannerView) getLayoutInflater()
    575                 .inflate(R.layout.input_banner, sceneContainer, false);
    576         SelectInputView selectInputView = (SelectInputView) getLayoutInflater()
    577                 .inflate(R.layout.select_input, sceneContainer, false);
    578         selectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() {
    579             @Override
    580             public void onTunerInputSelected() {
    581                 Channel currentChannel = mChannelTuner.getCurrentChannel();
    582                 if (currentChannel != null && !currentChannel.isPassthrough()) {
    583                     hideOverlays();
    584                 } else {
    585                     tuneToLastWatchedChannelForTunerInput();
    586                 }
    587             }
    588 
    589             @Override
    590             public void onPassthroughInputSelected(TvInputInfo input) {
    591                 Channel currentChannel = mChannelTuner.getCurrentChannel();
    592                 String currentInputId = currentChannel == null ? null : currentChannel.getInputId();
    593                 if (TextUtils.equals(input.getId(), currentInputId)) {
    594                     hideOverlays();
    595                 } else {
    596                     tuneToChannel(Channel.createPassthroughChannel(input.getId()));
    597                 }
    598             }
    599 
    600             private void hideOverlays() {
    601                 getOverlayManager().hideOverlays(
    602                         TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
    603                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
    604                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
    605                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
    606                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
    607             }
    608         });
    609         mSearchFragment = new ProgramGuideSearchFragment();
    610         mOverlayManager = new TvOverlayManager(this, mChannelTuner,
    611                 mKeypadChannelSwitchView, mChannelBannerView, inputBannerView,
    612                 selectInputView, sceneContainer, mSearchFragment);
    613 
    614         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    615         mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
    616 
    617         mMediaSession = new MediaSession(this, MEDIA_SESSION_TAG);
    618         mMediaSession.setCallback(new MediaSession.Callback() {
    619             @Override
    620             public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
    621                 // Consume the media button event here. Should not send it to other apps.
    622                 return true;
    623             }
    624         });
    625         mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
    626                 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
    627         mNowPlayingCardWidth = getResources().getDimensionPixelSize(
    628                 R.dimen.notif_card_img_max_width);
    629         mNowPlayingCardHeight = getResources().getDimensionPixelSize(R.dimen.notif_card_img_height);
    630 
    631         mTvViewUiManager.restoreDisplayMode(false);
    632         if (!handleIntent(getIntent())) {
    633             finish();
    634             return;
    635         }
    636 
    637         mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this,
    638                 new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() {
    639                     @Override
    640                     public void onAc3PassthroughCapabilityChange(boolean capability) {
    641                         mAc3PassthroughSupported = capability;
    642                     }
    643                 });
    644         mAudioCapabilitiesReceiver.register();
    645 
    646         mAccessibilityManager =
    647                 (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
    648         mSendConfigInfoRecurringRunner = new RecurringRunner(this, TimeUnit.DAYS.toMillis(1),
    649                 new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper), null);
    650         mSendConfigInfoRecurringRunner.start();
    651         mChannelStatusRecurringRunner = SendChannelStatusRunnable
    652                 .startChannelStatusRecurringRunner(this, mTracker, mChannelDataManager);
    653 
    654         // To avoid not updating Rating systems when changing language.
    655         mTvInputManagerHelper.getContentRatingsManager().update();
    656 
    657         initForTest();
    658     }
    659 
    660     @Override
    661     public void onRequestPermissionsResult(int requestCode, String[] permissions,
    662             int[] grantResults) {
    663         if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) {
    664             if (grantResults != null && grantResults.length > 0
    665                     && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    666                 // Start reload of dependent data
    667                 mChannelDataManager.reload();
    668                 mProgramDataManager.reload();
    669 
    670                 // Restart live channels.
    671                 Intent intent = getIntent();
    672                 finish();
    673                 startActivity(intent);
    674             } else {
    675                 Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied,
    676                         Toast.LENGTH_LONG).show();
    677                 finish();
    678             }
    679         }
    680     }
    681 
    682     @Override
    683     public void onAttachedToWindow() {
    684         super.onAttachedToWindow();
    685         WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams(
    686                 WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, 0, PixelFormat.TRANSPARENT);
    687         windowParams.token = getWindow().getDecorView().getWindowToken();
    688         ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).addView(mOverlayRootView,
    689                 windowParams);
    690     }
    691 
    692     @Override
    693     public void onDetachedFromWindow() {
    694         super.onDetachedFromWindow();
    695         ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).removeView(mOverlayRootView);
    696     }
    697 
    698     private int getDesiredBlockScreenType() {
    699         if (!mActivityResumed) {
    700             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
    701         }
    702         if (isUnderShrunkenTvView()) {
    703             return TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW;
    704         }
    705         if (mOverlayManager.needHideTextOnMainView()) {
    706             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
    707         }
    708         SafeDismissDialogFragment currentDialog = mOverlayManager.getCurrentDialog();
    709         if (currentDialog != null) {
    710             // If PIN dialog is shown for unblocking the channel lock or content ratings lock,
    711             // keeping the unlocking message is more natural instead of changing it.
    712             if (currentDialog instanceof PinDialogFragment) {
    713                 int type = ((PinDialogFragment) currentDialog).getType();
    714                 if (type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL
    715                         || type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) {
    716                     return TunableTvView.BLOCK_SCREEN_TYPE_NORMAL;
    717                 }
    718             }
    719             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
    720         }
    721         if (mOverlayManager.isSetupFragmentActive()
    722                 || mOverlayManager.isNewSourcesFragmentActive()) {
    723             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
    724         }
    725         return TunableTvView.BLOCK_SCREEN_TYPE_NORMAL;
    726     }
    727 
    728     @Override
    729     protected void onNewIntent(Intent intent) {
    730         mOverlayManager.getSideFragmentManager().hideAll(false);
    731         if (!handleIntent(intent) && !mActivityStarted) {
    732             // If the activity is stopped and not destroyed, finish the activity.
    733             // Otherwise, just ignore the intent.
    734             finish();
    735         }
    736     }
    737 
    738     @Override
    739     protected void onStart() {
    740         if (DEBUG) Log.d(TAG,"onStart()");
    741         super.onStart();
    742         mScreenOffIntentReceived = false;
    743         mActivityStarted = true;
    744         mTracker.sendMainStart();
    745         mMainDurationTimer.start();
    746 
    747         applyParentalControlSettings();
    748         IntentFilter intentFilter = new IntentFilter();
    749         intentFilter.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
    750         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
    751         intentFilter.addAction(Intent.ACTION_SCREEN_ON);
    752         registerReceiver(mBroadcastReceiver, intentFilter);
    753 
    754         Intent notificationIntent = new Intent(this, NotificationService.class);
    755         notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION);
    756         startService(notificationIntent);
    757     }
    758 
    759     @Override
    760     protected void onResume() {
    761         if (DEBUG) Log.d(TAG, "onResume()");
    762         super.onResume();
    763         if (!PermissionUtils.hasAccessAllEpg(this)
    764                 && checkSelfPermission(PERMISSION_READ_TV_LISTINGS)
    765                     != PackageManager.PERMISSION_GRANTED) {
    766             requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS},
    767                     PERMISSIONS_REQUEST_READ_TV_LISTINGS);
    768         }
    769         mTracker.sendScreenView(SCREEN_NAME);
    770 
    771         SystemProperties.updateSystemProperties();
    772         mNeedShowBackKeyGuide = true;
    773         mActivityResumed = true;
    774         mShowNewSourcesFragment = true;
    775         mOtherActivityLaunched = false;
    776         int result = mAudioManager.requestAudioFocus(MainActivity.this,
    777                 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
    778         mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ?
    779                 AudioManager.AUDIOFOCUS_GAIN : AudioManager.AUDIOFOCUS_LOSS;
    780         setVolumeByAudioFocusStatus();
    781 
    782         if (mTvView.isPlaying()) {
    783             // Every time onResume() is called the activity will be assumed to not have requested
    784             // visible behind.
    785             requestVisibleBehind(true);
    786         }
    787         if (mChannelTuner.areAllChannelsLoaded()) {
    788             SetupUtils.getInstance(this).markNewChannelsBrowsable();
    789             resumeTvIfNeeded();
    790             resumePipIfNeeded();
    791         }
    792         mOverlayManager.showMenuWithTimeShiftPauseIfNeeded();
    793 
    794         // Note: The following codes are related to pop up an overlay UI after resume.
    795         // When the following code is changed, please check the variable
    796         // willShowOverlayUiAfterResume in updateChannelBannerAndShowIfNeeded.
    797         if (mInputToSetUp != null) {
    798             startSetupActivity(mInputToSetUp, false);
    799             mInputToSetUp = null;
    800         } else if (mShowProgramGuide) {
    801             mShowProgramGuide = false;
    802             mHandler.post(new Runnable() {
    803                 // This will delay the start of the animation until after the Live Channel app is
    804                 // shown. Without this the animation is completed before it is actually visible on
    805                 // the screen.
    806                 @Override
    807                 public void run() {
    808                     mOverlayManager.showProgramGuide();
    809                 }
    810             });
    811         } else if (mShowSelectInputView) {
    812             mShowSelectInputView = false;
    813             mHandler.post(new Runnable() {
    814                 // mShowSelectInputView is true when the activity is started/resumed because the
    815                 // TV_INPUT button was pressed in a different app.
    816                 // This will delay the start of the animation until after the Live Channel app is
    817                 // shown. Without this the animation is completed before it is actually visible on
    818                 // the screen.
    819                 @Override
    820                 public void run() {
    821                     mOverlayManager.showSelectInputView();
    822                 }
    823             });
    824         }
    825     }
    826 
    827     @Override
    828     protected void onPause() {
    829         if (DEBUG) Log.d(TAG, "onPause()");
    830         finishChannelChangeIfNeeded();
    831         mActivityResumed = false;
    832         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_DEFAULT);
    833         mTvView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_NO_UI);
    834         if (mPipEnabled) {
    835             mTvViewUiManager.hidePipForPause();
    836         }
    837         mBackKeyPressed = false;
    838         mShowLockedChannelsTemporarily = false;
    839         mShouldTuneToTunerChannel = false;
    840         if (!mVisibleBehind) {
    841             mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
    842             mAudioManager.abandonAudioFocus(this);
    843             if (mMediaSession.isActive()) {
    844                 mMediaSession.setActive(false);
    845             }
    846             mTracker.sendScreenView("");
    847         } else {
    848             mTracker.sendScreenView(SCREEN_BEHIND_NAME);
    849         }
    850         super.onPause();
    851     }
    852 
    853     /**
    854      * Returns true if {@link #onResume} is called and {@link #onPause} is not called yet.
    855      */
    856     public boolean isActivityResumed() {
    857         return mActivityResumed;
    858     }
    859 
    860     /**
    861      * Returns true if {@link #onStart} is called and {@link #onStop} is not called yet.
    862      */
    863     public boolean isActivityStarted() {
    864         return mActivityStarted;
    865     }
    866 
    867     @Override
    868     public boolean requestVisibleBehind(boolean enable) {
    869         boolean state = super.requestVisibleBehind(enable);
    870         mVisibleBehind = state;
    871         return state;
    872     }
    873 
    874     private void resumeTvIfNeeded() {
    875         if (DEBUG) Log.d(TAG, "resumeTvIfNeeded()");
    876         if (!mTvView.isPlaying() || mInitChannelUri != null
    877                 || (mShouldTuneToTunerChannel && mChannelTuner.isCurrentChannelPassthrough())) {
    878             if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
    879                 // The target input may not be ready yet, especially, just after screen on.
    880                 String inputId = mInitChannelUri.getPathSegments().get(1);
    881                 TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(inputId);
    882                 if (input == null) {
    883                     input = mTvInputManagerHelper.getTvInputInfo(mParentInputIdWhenScreenOff);
    884                     if (input == null) {
    885                         SoftPreconditions.checkState(false, TAG, "Input disappear." + input);
    886                         finish();
    887                     } else {
    888                         mInitChannelUri =
    889                                 TvContract.buildChannelUriForPassthroughInput(input.getId());
    890                     }
    891                 }
    892             }
    893             mParentInputIdWhenScreenOff = null;
    894             startTv(mInitChannelUri);
    895             mInitChannelUri = null;
    896         }
    897         // Make sure TV app has the main TV view to handle the case that TvView is used in other
    898         // application.
    899         restoreMainTvView();
    900         mTvView.setBlockScreenType(getDesiredBlockScreenType());
    901     }
    902 
    903     private void resumePipIfNeeded() {
    904         if (mPipEnabled && !(mPipView.isPlaying() && mPipView.isShown())) {
    905             if (mPipInputManager.areInSamePipInput(
    906                     mChannelTuner.getCurrentChannel(), mPipChannel)) {
    907                 enablePipView(false, false);
    908             } else {
    909                 if (!mPipView.isPlaying()) {
    910                     startPip(false);
    911                 } else {
    912                     mTvViewUiManager.showPipForResume();
    913                 }
    914             }
    915         }
    916     }
    917 
    918     private void startTv(Uri channelUri) {
    919         if (DEBUG) Log.d(TAG, "startTv Uri=" + channelUri);
    920         if ((channelUri == null || !TvContract.isChannelUriForPassthroughInput(channelUri))
    921                 && mChannelTuner.isCurrentChannelPassthrough()) {
    922             // For passthrough TV input, channelUri is always given. If TV app is launched
    923             // by TV app icon in a launcher, channelUri is null. So if passthrough TV input
    924             // is playing, we stop the passthrough TV input.
    925             stopTv();
    926         }
    927         SoftPreconditions.checkState(TvContract.isChannelUriForPassthroughInput(channelUri)
    928                 || mChannelTuner.areAllChannelsLoaded(),
    929                 TAG, "startTV assumes that ChannelDataManager is already loaded.");
    930         if (mTvView.isPlaying()) {
    931             // TV has already started.
    932             if (channelUri == null) {
    933                 // Simply adjust the volume without tune.
    934                 setVolumeByAudioFocusStatus();
    935                 return;
    936             }
    937             if (channelUri.equals(mChannelTuner.getCurrentChannelUri())) {
    938                 // The requested channel is already tuned.
    939                 setVolumeByAudioFocusStatus();
    940                 return;
    941             }
    942             stopTv();
    943         }
    944         if (mChannelTuner.getCurrentChannel() != null) {
    945             Log.w(TAG, "The current channel should be reset before");
    946             mChannelTuner.resetCurrentChannel();
    947         }
    948         if (channelUri == null) {
    949             // If any initial channel id is not given, remember the last channel the user watched.
    950             long channelId = Utils.getLastWatchedChannelId(this);
    951             if (channelId != Channel.INVALID_ID) {
    952                 channelUri = TvContract.buildChannelUri(channelId);
    953             }
    954         }
    955 
    956         if (channelUri == null) {
    957             mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
    958         } else {
    959             if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
    960                 Channel channel = Channel.createPassthroughChannel(channelUri);
    961                 mChannelTuner.moveToChannel(channel);
    962             } else {
    963                 long channelId = ContentUris.parseId(channelUri);
    964                 Channel channel = mChannelDataManager.getChannel(channelId);
    965                 if (channel == null || !mChannelTuner.moveToChannel(channel)) {
    966                     mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
    967                     Log.w(TAG, "The requested channel (id=" + channelId + ") doesn't exist. "
    968                             + "The first channel will be tuned to.");
    969                 }
    970             }
    971         }
    972 
    973         mTvView.start(mTvInputManagerHelper);
    974         setVolumeByAudioFocusStatus();
    975         if (mRecordingUri != null) {
    976             playRecording(mRecordingUri);
    977             mRecordingUri = null;
    978         } else {
    979             tune();
    980         }
    981     }
    982 
    983     @Override
    984     protected void onStop() {
    985         if (DEBUG) Log.d(TAG, "onStop()");
    986         if (mScreenOffIntentReceived) {
    987             mScreenOffIntentReceived = false;
    988         } else {
    989             PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
    990             if (!powerManager.isInteractive()) {
    991                 // We added to check isInteractive as well as SCREEN_OFF intent, because
    992                 // calling timing of the intent SCREEN_OFF is not consistent. b/25953633.
    993                 // If we verify that checking isInteractive is enough, we can remove the logic
    994                 // for SCREEN_OFF intent.
    995                 markCurrentChannelDuringScreenOff();
    996             }
    997         }
    998         mActivityStarted = false;
    999         stopAll(false);
   1000         unregisterReceiver(mBroadcastReceiver);
   1001         mTracker.sendMainStop(mMainDurationTimer.reset());
   1002         super.onStop();
   1003     }
   1004 
   1005     /**
   1006      * Handles screen off to keep the current channel for next screen on.
   1007      */
   1008     private void markCurrentChannelDuringScreenOff() {
   1009         mInitChannelUri = mChannelTuner.getCurrentChannelUri();
   1010         if (mChannelTuner.isCurrentChannelPassthrough()) {
   1011             // When ACTION_SCREEN_OFF is invoked, some CEC devices may be already
   1012             // removed. So we need to get the input info from ChannelTuner instead of
   1013             // TvInputManagerHelper.
   1014             TvInputInfo input = mChannelTuner.getCurrentInputInfo();
   1015             mParentInputIdWhenScreenOff = input.getParentId();
   1016             if (DEBUG) Log.d(TAG, "Parent input: " + mParentInputIdWhenScreenOff);
   1017         }
   1018     }
   1019 
   1020     private void stopAll(boolean keepVisibleBehind) {
   1021         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
   1022         stopTv("stopAll()", keepVisibleBehind);
   1023         stopPip();
   1024     }
   1025 
   1026     public TvInputManagerHelper getTvInputManagerHelper() {
   1027         return mTvInputManagerHelper;
   1028     }
   1029 
   1030     /**
   1031      * Starts setup activity for the given input {@code input}.
   1032      *
   1033      * @param calledByPopup If true, startSetupActivity is invoked from the setup fragment.
   1034      */
   1035     public void startSetupActivity(TvInputInfo input, boolean calledByPopup) {
   1036         Intent intent = TvCommonUtils.createSetupIntent(input);
   1037         if (intent == null) {
   1038             Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT).show();
   1039             return;
   1040         }
   1041         // Even though other app can handle the intent, the setup launched by Live channels
   1042         // should go through Live channels SetupPassthroughActivity.
   1043         intent.setComponent(new ComponentName(this, SetupPassthroughActivity.class));
   1044         try {
   1045             // Now we know that the user intends to set up this input. Grant permission for writing
   1046             // EPG data.
   1047             SetupUtils.grantEpgPermission(this, input.getServiceInfo().packageName);
   1048 
   1049             mInputIdUnderSetup = input.getId();
   1050             mIsSetupActivityCalledByPopup = calledByPopup;
   1051             // Call requestVisibleBehind(false) before starting other activity.
   1052             // In Activity.requestVisibleBehind(false), this activity is scheduled to be stopped
   1053             // immediately if other activity is about to start. And this activity is scheduled to
   1054             // to be stopped again after onPause().
   1055             stopTv("startSetupActivity()", false);
   1056             startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
   1057         } catch (ActivityNotFoundException e) {
   1058             mInputIdUnderSetup = null;
   1059             Toast.makeText(this, getString(R.string.msg_unable_to_start_setup_activity,
   1060                     input.loadLabel(this)), Toast.LENGTH_SHORT).show();
   1061             return;
   1062         }
   1063         if (calledByPopup) {
   1064             mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
   1065                     | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
   1066         } else {
   1067             mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
   1068                     | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY);
   1069         }
   1070     }
   1071 
   1072     public boolean hasCaptioningSettingsActivity() {
   1073         return Utils.isIntentAvailable(this, new Intent(Settings.ACTION_CAPTIONING_SETTINGS));
   1074     }
   1075 
   1076     public void startSystemCaptioningSettingsActivity() {
   1077         Intent intent = new Intent(Settings.ACTION_CAPTIONING_SETTINGS);
   1078         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
   1079                 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY);
   1080         try {
   1081             startActivityForResultSafe(intent, REQUEST_CODE_START_SYSTEM_CAPTIONING_SETTINGS);
   1082         } catch (ActivityNotFoundException e) {
   1083             Toast.makeText(this, getString(R.string.msg_unable_to_start_system_captioning_settings),
   1084                     Toast.LENGTH_SHORT).show();
   1085         }
   1086     }
   1087 
   1088     public ChannelDataManager getChannelDataManager() {
   1089         return mChannelDataManager;
   1090     }
   1091 
   1092     public ProgramDataManager getProgramDataManager() {
   1093         return mProgramDataManager;
   1094     }
   1095 
   1096     public PipInputManager getPipInputManager() {
   1097         return mPipInputManager;
   1098     }
   1099 
   1100     public TvOptionsManager getTvOptionsManager() {
   1101         return mTvOptionsManager;
   1102     }
   1103 
   1104     public TvViewUiManager getTvViewUiManager() {
   1105         return mTvViewUiManager;
   1106     }
   1107 
   1108     public TimeShiftManager getTimeShiftManager() {
   1109         return mTimeShiftManager;
   1110     }
   1111 
   1112     /**
   1113      * Returns the instance of {@link TvOverlayManager}.
   1114      */
   1115     public TvOverlayManager getOverlayManager() {
   1116         return mOverlayManager;
   1117     }
   1118 
   1119     public Channel getCurrentChannel() {
   1120         return mTvView.isRecordingPlayback() ? mTvView.getCurrentChannel()
   1121                 : mChannelTuner.getCurrentChannel();
   1122     }
   1123 
   1124     public long getCurrentChannelId() {
   1125         if (mTvView.isRecordingPlayback()) {
   1126             Channel channel = mTvView.getCurrentChannel();
   1127             return channel == null ? Channel.INVALID_ID : channel.getId();
   1128         }
   1129         return mChannelTuner.getCurrentChannelId();
   1130     }
   1131 
   1132     /**
   1133      * Returns true if the current connected TV supports AC3 passthough.
   1134      */
   1135     public boolean isAc3PassthroughSupported() {
   1136         return mAc3PassthroughSupported;
   1137     }
   1138 
   1139     /**
   1140      * Returns the current program which the user is watching right now.<p>
   1141      *
   1142      * If the time shifting is available, it can be a past program.
   1143      */
   1144     public Program getCurrentProgram() {
   1145         return getCurrentProgram(true);
   1146     }
   1147 
   1148     /**
   1149      * Returns {@code true}, if this view is the recording playback mode.
   1150      */
   1151     public boolean isRecordingPlayback() {
   1152         return mTvView.isRecordingPlayback();
   1153     }
   1154 
   1155     /**
   1156      * Returns the recording which is being played right now.
   1157      */
   1158     public RecordedProgram getPlayingRecordedProgram() {
   1159         return mTvView.getPlayingRecordedProgram();
   1160     }
   1161 
   1162     /**
   1163      * Returns the current program which the user is watching right now.<p>
   1164      *
   1165      * @param applyTimeShifted If it is true and the time shifting is available, it can be
   1166      *        a past program.
   1167      */
   1168     public Program getCurrentProgram(boolean applyTimeShifted) {
   1169         if (applyTimeShifted && mTimeShiftManager.isAvailable()) {
   1170             return mTimeShiftManager.getCurrentProgram();
   1171         }
   1172         return mProgramDataManager.getCurrentProgram(getCurrentChannelId());
   1173     }
   1174 
   1175     /**
   1176      * Returns the current playing time in milliseconds.<p>
   1177      *
   1178      * If the time shifting is available, the time is the playing position of the program,
   1179      * otherwise, the system current time.
   1180      */
   1181     public long getCurrentPlayingPosition() {
   1182         if (mTimeShiftManager.isAvailable()) {
   1183             return mTimeShiftManager.getCurrentPositionMs();
   1184         }
   1185         return System.currentTimeMillis();
   1186     }
   1187 
   1188     public Channel getBrowsableChannel() {
   1189         // TODO: mChannelMap could be dirty for a while when the browsablity of channels
   1190         // are changed. In that case, we shouldn't use the value from mChannelMap.
   1191         Channel curChannel = mChannelTuner.getCurrentChannel();
   1192         if (curChannel != null && curChannel.isBrowsable()) {
   1193             return curChannel;
   1194         } else {
   1195             return mChannelTuner.getAdjacentBrowsableChannel(true);
   1196         }
   1197     }
   1198 
   1199     /**
   1200      * Call {@link Activity#startActivity} in a safe way.
   1201      *
   1202      * @see LauncherActivity
   1203      */
   1204     public void startActivitySafe(Intent intent) {
   1205         LauncherActivity.startActivitySafe(this, intent);
   1206     }
   1207 
   1208     /**
   1209      * Call {@link Activity#startActivityForResult} in a safe way.
   1210      *
   1211      * @see LauncherActivity
   1212      */
   1213     private void startActivityForResultSafe(Intent intent, int requestCode) {
   1214         LauncherActivity.startActivityForResultSafe(this, intent, requestCode);
   1215     }
   1216 
   1217     /**
   1218      * Show settings fragment.
   1219      */
   1220     public void showSettingsFragment() {
   1221         if (!mChannelTuner.areAllChannelsLoaded()) {
   1222             // Show ChannelSourcesFragment only if all the channels are loaded.
   1223             return;
   1224         }
   1225         Channel currentChannel = mChannelTuner.getCurrentChannel();
   1226         long channelId = currentChannel == null ? Channel.INVALID_ID : currentChannel.getId();
   1227         mOverlayManager.getSideFragmentManager().show(new SettingsFragment(channelId));
   1228     }
   1229 
   1230     public void showMerchantCollection() {
   1231         startActivitySafe(OnboardingUtils.PLAY_STORE_INTENT);
   1232     }
   1233 
   1234     /**
   1235      * It is called when shrunken TvView is desired, such as EditChannelFragment and
   1236      * ChannelsLockedFragment.
   1237      */
   1238     public void startShrunkenTvView(boolean showLockedChannelsTemporarily,
   1239             boolean willMainViewBeTunerInput) {
   1240         mChannelBeforeShrunkenTvView = mTvView.getCurrentChannel();
   1241         mWasChannelUnblockedBeforeShrunkenByUser = mIsCurrentChannelUnblockedByUser;
   1242         mAllowedRatingBeforeShrunken = mLastAllowedRatingForCurrentChannel;
   1243 
   1244         if (willMainViewBeTunerInput && mChannelTuner.isCurrentChannelPassthrough()
   1245                 && mPipEnabled) {
   1246             mPipChannelBeforeShrunkenTvView = mPipChannel;
   1247             enablePipView(false, false);
   1248         } else {
   1249             mPipChannelBeforeShrunkenTvView = null;
   1250         }
   1251         mTvViewUiManager.startShrunkenTvView();
   1252 
   1253         if (showLockedChannelsTemporarily) {
   1254             mShowLockedChannelsTemporarily = true;
   1255             checkChannelLockNeeded(mTvView);
   1256         }
   1257 
   1258         mTvView.setBlockScreenType(getDesiredBlockScreenType());
   1259     }
   1260 
   1261     /**
   1262      * It is called when shrunken TvView is no longer desired, such as EditChannelFragment and
   1263      * ChannelsLockedFragment.
   1264      */
   1265     public void endShrunkenTvView() {
   1266         mTvViewUiManager.endShrunkenTvView();
   1267         mIsCompletingShrunkenTvView = true;
   1268 
   1269         Channel returnChannel = mChannelBeforeShrunkenTvView;
   1270         if (returnChannel == null
   1271                 || (!returnChannel.isPassthrough() && !returnChannel.isBrowsable())) {
   1272             // Try to tune to the next best channel instead.
   1273             returnChannel = getBrowsableChannel();
   1274         }
   1275         mShowLockedChannelsTemporarily = false;
   1276 
   1277         // The current channel is mTvView.getCurrentChannel() and need to tune to the returnChannel.
   1278         if (!Objects.equals(mTvView.getCurrentChannel(), returnChannel)) {
   1279             final Channel channel = returnChannel;
   1280             Runnable tuneAction = new Runnable() {
   1281                 @Override
   1282                 public void run() {
   1283                     tuneToChannel(channel);
   1284                     if (mChannelBeforeShrunkenTvView == null
   1285                             || !mChannelBeforeShrunkenTvView.equals(channel)) {
   1286                         Utils.setLastWatchedChannel(MainActivity.this, channel);
   1287                     }
   1288                     mIsCompletingShrunkenTvView = false;
   1289                     mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
   1290                     mTvView.setBlockScreenType(getDesiredBlockScreenType());
   1291                     if (mPipChannelBeforeShrunkenTvView != null) {
   1292                         enablePipView(true, false);
   1293                         mPipChannelBeforeShrunkenTvView = null;
   1294                     }
   1295                 }
   1296             };
   1297             mTvViewUiManager.fadeOutTvView(tuneAction);
   1298             // Will automatically fade-in when video becomes available.
   1299         } else {
   1300             checkChannelLockNeeded(mTvView);
   1301             mIsCompletingShrunkenTvView = false;
   1302             mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
   1303             mTvView.setBlockScreenType(getDesiredBlockScreenType());
   1304             if (mPipChannelBeforeShrunkenTvView != null) {
   1305                 enablePipView(true, false);
   1306                 mPipChannelBeforeShrunkenTvView = null;
   1307             }
   1308         }
   1309     }
   1310 
   1311     private boolean isUnderShrunkenTvView() {
   1312         return mTvViewUiManager.isUnderShrunkenTvView() || mIsCompletingShrunkenTvView;
   1313     }
   1314 
   1315     @Override
   1316     public void onActivityResult(int requestCode, int resultCode, Intent data) {
   1317         switch (requestCode) {
   1318             case REQUEST_CODE_START_SETUP_ACTIVITY:
   1319                 if (resultCode == RESULT_OK) {
   1320                     int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup);
   1321                     String text;
   1322                     if (count > 0) {
   1323                         text = getResources().getQuantityString(R.plurals.msg_channel_added,
   1324                                 count, count);
   1325                     } else {
   1326                         text = getString(R.string.msg_no_channel_added);
   1327                     }
   1328                     Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
   1329                     mInputIdUnderSetup = null;
   1330                     if (mChannelTuner.getCurrentChannel() == null) {
   1331                         mChannelTuner.moveToAdjacentBrowsableChannel(true);
   1332                     }
   1333                     if (mTunePending) {
   1334                         tune();
   1335                     }
   1336                 } else {
   1337                     mInputIdUnderSetup = null;
   1338                 }
   1339                 if (!mIsSetupActivityCalledByPopup) {
   1340                     mOverlayManager.getSideFragmentManager().showSidePanel(false);
   1341                 }
   1342                 break;
   1343             case REQUEST_CODE_START_SYSTEM_CAPTIONING_SETTINGS:
   1344                 mOverlayManager.getSideFragmentManager().showSidePanel(false);
   1345                 break;
   1346         }
   1347         if (data != null) {
   1348             String errorMessage = data.getStringExtra(LauncherActivity.ERROR_MESSAGE);
   1349             if (!TextUtils.isEmpty(errorMessage)) {
   1350                 Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
   1351             }
   1352         }
   1353     }
   1354 
   1355     @Override
   1356     public View findViewById(int id) {
   1357         // In order to locate fragments in non-application window, we should override findViewById.
   1358         // Internally, Activity.findViewById is called to attach a view of a fragment into its
   1359         // container. Without the override, we'll get crash during the fragment attachment.
   1360         View v = mOverlayRootView != null ? mOverlayRootView.findViewById(id) : null;
   1361         return v == null ? super.findViewById(id) : v;
   1362     }
   1363 
   1364     @Override
   1365     public boolean dispatchKeyEvent(KeyEvent event) {
   1366         if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
   1367         // If an activity is closed on a back key down event, back key down events with none zero
   1368         // repeat count or a back key up event can be happened without the first back key down
   1369         // event which should be ignored in this activity.
   1370         if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
   1371             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
   1372                 mBackKeyPressed = true;
   1373             }
   1374             if (!mBackKeyPressed) {
   1375                 return true;
   1376             }
   1377             if (event.getAction() == KeyEvent.ACTION_UP) {
   1378                 mBackKeyPressed = false;
   1379             }
   1380         }
   1381 
   1382         // When side panel is closing, it has the focus.
   1383         // Keep the focus, but just don't deliver the key events.
   1384         if ((mOverlayRootView.hasFocusable()
   1385                 && !mOverlayManager.getSideFragmentManager().isHiding())
   1386                 || mOverlayManager.getSideFragmentManager().isActive()) {
   1387             return super.dispatchKeyEvent(event);
   1388         }
   1389         if (BLACKLIST_KEYCODE_TO_TIS.contains(event.getKeyCode())
   1390                 || KeyEvent.isGamepadButton(event.getKeyCode())) {
   1391             // If the event is in blacklisted or gamepad key, do not pass it to session.
   1392             // Gamepad keys are blacklisted to support TV UIs and here's the detail.
   1393             // If there's a TIS granted RECEIVE_INPUT_EVENT, TIF sends key events to TIS
   1394             // and return immediately saying that the event is handled.
   1395             // In this case, fallback key will be injected but with FLAG_CANCELED
   1396             // while gamepads support DPAD_CENTER and BACK by fallback.
   1397             // Since we don't expect that TIS want to handle gamepad buttons now,
   1398             // blacklist gamepad buttons and wait for next fallback keys.
   1399             // TODO) Need to consider other fallback keys (e.g. ESCAPE)
   1400             return super.dispatchKeyEvent(event);
   1401         }
   1402         return dispatchKeyEventToSession(event) || super.dispatchKeyEvent(event);
   1403     }
   1404 
   1405     @Override
   1406     public void onAudioFocusChange(int focusChange) {
   1407         mAudioFocusStatus = focusChange;
   1408         setVolumeByAudioFocusStatus();
   1409     }
   1410 
   1411     /**
   1412      * Notifies the key input focus is changed to the TV view.
   1413      */
   1414     public void updateKeyInputFocus() {
   1415         mHandler.post(new Runnable() {
   1416             @Override
   1417             public void run() {
   1418                 mTvView.setBlockScreenType(getDesiredBlockScreenType());
   1419             }
   1420         });
   1421     }
   1422 
   1423     // It should be called before onResume.
   1424     private boolean handleIntent(Intent intent) {
   1425         // Reset the closed caption settings when the activity is 1)created or 2) restarted.
   1426         // And do not reset while TvView is playing.
   1427         if (!mTvView.isPlaying()) {
   1428             mCaptionSettings = new CaptionSettings(this);
   1429         }
   1430 
   1431         // Handle the passed key press, if any. Note that only the key codes that are currently
   1432         // handled in the TV app will be handled via Intent.
   1433         // TODO: Consider defining a separate intent filter as passing data of mime type
   1434         // vnd.android.cursor.item/channel isn't really necessary here.
   1435         int keyCode = intent.getIntExtra(Utils.EXTRA_KEY_KEYCODE, KeyEvent.KEYCODE_UNKNOWN);
   1436         if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
   1437             if (DEBUG) Log.d(TAG, "Got an intent with keycode: " + keyCode);
   1438             KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
   1439             onKeyUp(keyCode, event);
   1440             return true;
   1441         }
   1442         mShouldTuneToTunerChannel = intent.getBooleanExtra(Utils.EXTRA_KEY_FROM_LAUNCHER, false);
   1443         mInitChannelUri = null;
   1444 
   1445         String extraAction = intent.getStringExtra(Utils.EXTRA_KEY_ACTION);
   1446         if (!TextUtils.isEmpty(extraAction)) {
   1447             if (DEBUG) Log.d(TAG, "Got an extra action: " + extraAction);
   1448             if (Utils.EXTRA_ACTION_SHOW_TV_INPUT.equals(extraAction)) {
   1449                 String lastWatchedChannelUri = Utils.getLastWatchedChannelUri(this);
   1450                 if (lastWatchedChannelUri != null) {
   1451                     mInitChannelUri = Uri.parse(lastWatchedChannelUri);
   1452                 }
   1453                 mShowSelectInputView = true;
   1454             }
   1455         }
   1456 
   1457         if (CommonFeatures.DVR.isEnabled(this) && BuildCompat.isAtLeastN()) {
   1458             mRecordingUri = intent.getParcelableExtra(Utils.EXTRA_KEY_RECORDING_URI);
   1459             if (mRecordingUri != null) {
   1460                 return true;
   1461             }
   1462         }
   1463 
   1464         // TODO: remove the checkState once N API is finalized.
   1465         SoftPreconditions.checkState(TvInputManager.ACTION_SETUP_INPUTS.equals(
   1466                 "android.media.tv.action.SETUP_INPUTS"));
   1467         if (TvInputManager.ACTION_SETUP_INPUTS.equals(intent.getAction())) {
   1468             runAfterAttachedToWindow(new Runnable() {
   1469                 @Override
   1470                 public void run() {
   1471                     mOverlayManager.showSetupFragment();
   1472                 }
   1473             });
   1474         } else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
   1475             Uri uri = intent.getData();
   1476             try {
   1477                 mSource = uri.getQueryParameter(Utils.PARAM_SOURCE);
   1478             } catch (UnsupportedOperationException e) {
   1479                 // ignore this exception.
   1480             }
   1481             // When the URI points to the programs (directory, not an individual item), go to the
   1482             // program guide. The intention here is to respond to
   1483             // "content://android.media.tv/program", not "content://android.media.tv/program/XXX".
   1484             // Later, we might want to add handling of individual programs too.
   1485             if (Utils.isProgramsUri(uri)) {
   1486                 // The given data is a programs URI. Open the Program Guide.
   1487                 mShowProgramGuide = true;
   1488                 return true;
   1489             }
   1490             // In case the channel is given explicitly, use it.
   1491             mInitChannelUri = uri;
   1492             if (DEBUG) Log.d(TAG, "ACTION_VIEW with " + mInitChannelUri);
   1493             if (Channels.CONTENT_URI.equals(mInitChannelUri)) {
   1494                 // Tune to default channel.
   1495                 mInitChannelUri = null;
   1496                 mShouldTuneToTunerChannel = true;
   1497                 return true;
   1498             }
   1499             if ((!Utils.isChannelUriForOneChannel(mInitChannelUri)
   1500                     && !Utils.isChannelUriForInput(mInitChannelUri))) {
   1501                 Log.w(TAG, "Malformed channel uri " + mInitChannelUri
   1502                         + " tuning to default instead");
   1503                 mInitChannelUri = null;
   1504                 return true;
   1505             }
   1506             mTuneParams = intent.getExtras();
   1507             if (mTuneParams == null) {
   1508                 mTuneParams = new Bundle();
   1509             }
   1510             if (Utils.isChannelUriForTunerInput(mInitChannelUri)) {
   1511                 long channelId = ContentUris.parseId(mInitChannelUri);
   1512                 mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId);
   1513             } else if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
   1514                 // If mInitChannelUri is for a passthrough TV input.
   1515                 String inputId = mInitChannelUri.getPathSegments().get(1);
   1516                 TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(inputId);
   1517                 if (input == null) {
   1518                     mInitChannelUri = null;
   1519                     Toast.makeText(this, R.string.msg_no_specific_input, Toast.LENGTH_SHORT).show();
   1520                     return false;
   1521                 } else if (!input.isPassthroughInput()) {
   1522                     mInitChannelUri = null;
   1523                     Toast.makeText(this, R.string.msg_not_passthrough_input, Toast.LENGTH_SHORT)
   1524                             .show();
   1525                     return false;
   1526                 }
   1527             } else if (mInitChannelUri != null) {
   1528                 // Handle the URI built by TvContract.buildChannelsUriForInput().
   1529                 // TODO: Change hard-coded "input" to TvContract.PARAM_INPUT.
   1530                 String inputId = mInitChannelUri.getQueryParameter("input");
   1531                 long channelId = Utils.getLastWatchedChannelIdForInput(this, inputId);
   1532                 if (channelId == Channel.INVALID_ID) {
   1533                     String[] projection = { Channels._ID };
   1534                     try (Cursor cursor = getContentResolver().query(uri, projection,
   1535                             null, null, null)) {
   1536                         if (cursor != null && cursor.moveToNext()) {
   1537                             channelId = cursor.getLong(0);
   1538                         }
   1539                     }
   1540                 }
   1541                 if (channelId == Channel.INVALID_ID) {
   1542                     // Couldn't find any channel probably because the input hasn't been set up.
   1543                     // Try to set it up.
   1544                     mInitChannelUri = null;
   1545                     mInputToSetUp = mTvInputManagerHelper.getTvInputInfo(inputId);
   1546                 } else {
   1547                     mInitChannelUri = TvContract.buildChannelUri(channelId);
   1548                     mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId);
   1549                 }
   1550             }
   1551         }
   1552         return true;
   1553     }
   1554 
   1555     private void setVolumeByAudioFocusStatus() {
   1556         if (mPipSound == TvSettings.PIP_SOUND_MAIN) {
   1557             setVolumeByAudioFocusStatus(mTvView);
   1558         } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW
   1559             setVolumeByAudioFocusStatus(mPipView);
   1560         }
   1561     }
   1562 
   1563     private void setVolumeByAudioFocusStatus(TunableTvView tvView) {
   1564         SoftPreconditions.checkState(tvView == mTvView || tvView == mPipView);
   1565         if (tvView.isPlaying()) {
   1566             switch (mAudioFocusStatus) {
   1567                 case AudioManager.AUDIOFOCUS_GAIN:
   1568                     tvView.setStreamVolume(AUDIO_MAX_VOLUME);
   1569                     break;
   1570                 case AudioManager.AUDIOFOCUS_LOSS:
   1571                 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
   1572                     tvView.setStreamVolume(AUDIO_MIN_VOLUME);
   1573                     break;
   1574                 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
   1575                     tvView.setStreamVolume(AUDIO_DUCKING_VOLUME);
   1576                     break;
   1577             }
   1578         }
   1579         if (tvView == mTvView) {
   1580             if (mPipView != null && mPipView.isPlaying()) {
   1581                 mPipView.setStreamVolume(AUDIO_MIN_VOLUME);
   1582             }
   1583         } else {  // tvView == mPipView
   1584             if (mTvView != null && mTvView.isPlaying()) {
   1585                 mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
   1586             }
   1587         }
   1588     }
   1589 
   1590     private void stopTv() {
   1591         stopTv(null, false);
   1592     }
   1593 
   1594     private void stopTv(String logForCaller, boolean keepVisibleBehind) {
   1595         if (logForCaller != null) {
   1596             Log.i(TAG, "stopTv is called at " + logForCaller + ".");
   1597         } else {
   1598             if (DEBUG) Log.d(TAG, "stopTv()");
   1599         }
   1600         if (mTvView.isPlaying()) {
   1601             mTvView.stop();
   1602             if (!keepVisibleBehind) {
   1603                 requestVisibleBehind(false);
   1604             }
   1605             mAudioManager.abandonAudioFocus(this);
   1606             if (mMediaSession.isActive()) {
   1607                 mMediaSession.setActive(false);
   1608             }
   1609         }
   1610         TvApplication.getSingletons(this).getMainActivityWrapper()
   1611                 .notifyCurrentChannelChange(this, null);
   1612         mChannelTuner.resetCurrentChannel();
   1613         mTunePending = false;
   1614     }
   1615 
   1616     private boolean isPlaying() {
   1617         return mTvView.isPlaying() && mTvView.getCurrentChannel() != null;
   1618     }
   1619 
   1620     private void startPip(final boolean fromUserInteraction) {
   1621         if (mPipChannel == null) {
   1622             Log.w(TAG, "PIP channel id is an invalid id.");
   1623             return;
   1624         }
   1625         if (DEBUG) Log.d(TAG, "startPip() " + mPipChannel);
   1626         mPipView.start(mTvInputManagerHelper);
   1627         boolean success = mPipView.tuneTo(mPipChannel, null, new OnTuneListener() {
   1628             @Override
   1629             public void onUnexpectedStop(Channel channel) {
   1630                 Log.w(TAG, "The PIP is Unexpectedly stopped");
   1631                 enablePipView(false, false);
   1632             }
   1633 
   1634             @Override
   1635             public void onTuneFailed(Channel channel) {
   1636                 Log.w(TAG, "Fail to start the PIP during channel tuning");
   1637                 if (fromUserInteraction) {
   1638                     Toast.makeText(MainActivity.this, R.string.msg_no_pip_support,
   1639                             Toast.LENGTH_SHORT).show();
   1640                     enablePipView(false, false);
   1641                 }
   1642             }
   1643 
   1644             @Override
   1645             public void onStreamInfoChanged(StreamInfo info) {
   1646                 mTvViewUiManager.updatePipView();
   1647                 mHandler.removeCallbacks(mRestoreMainViewRunnable);
   1648                 restoreMainTvView();
   1649             }
   1650 
   1651             @Override
   1652             public void onChannelRetuned(Uri channel) {
   1653                 if (channel == null) {
   1654                     return;
   1655                 }
   1656                 Channel currentChannel =
   1657                         mChannelDataManager.getChannel(ContentUris.parseId(channel));
   1658                 if (currentChannel == null) {
   1659                     Log.e(TAG, "onChannelRetuned is called from PIP input but can't find a channel"
   1660                             + " with the URI " + channel);
   1661                     return;
   1662                 }
   1663                 if (isChannelChangeKeyDownReceived()) {
   1664                     // Ignore this message if the user is changing the channel.
   1665                     return;
   1666                 }
   1667                 mPipChannel = currentChannel;
   1668                 mPipView.setCurrentChannel(mPipChannel);
   1669             }
   1670 
   1671             @Override
   1672             public void onContentBlocked() {
   1673                 updateMediaSession();
   1674             }
   1675 
   1676             @Override
   1677             public void onContentAllowed() {
   1678                 updateMediaSession();
   1679             }
   1680         });
   1681         if (!success) {
   1682             Log.w(TAG, "Fail to start the PIP");
   1683             return;
   1684         }
   1685         if (fromUserInteraction) {
   1686             checkChannelLockNeeded(mPipView);
   1687         }
   1688         // Explicitly make the PIP view main to make the selected input an HDMI-CEC active source.
   1689         mPipView.setMain();
   1690         scheduleRestoreMainTvView();
   1691         mTvViewUiManager.onPipStart();
   1692         setVolumeByAudioFocusStatus();
   1693     }
   1694 
   1695     private void scheduleRestoreMainTvView() {
   1696         mHandler.removeCallbacks(mRestoreMainViewRunnable);
   1697         mHandler.postDelayed(mRestoreMainViewRunnable, TVVIEW_SET_MAIN_TIMEOUT_MS);
   1698     }
   1699 
   1700     private void stopPip() {
   1701         if (DEBUG) Log.d(TAG, "stopPip");
   1702         if (mPipView.isPlaying()) {
   1703             mPipView.stop();
   1704             mPipSwap = false;
   1705             mTvViewUiManager.onPipStop();
   1706         }
   1707     }
   1708 
   1709     /**
   1710      * Says {@code text} when accessibility is turned on.
   1711      */
   1712     public void sendAccessibilityText(String text) {
   1713         if (mAccessibilityManager.isEnabled()) {
   1714             AccessibilityEvent event = AccessibilityEvent.obtain();
   1715             event.setClassName(getClass().getName());
   1716             event.setPackageName(getPackageName());
   1717             event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
   1718             event.getText().add(text);
   1719             mAccessibilityManager.sendAccessibilityEvent(event);
   1720         }
   1721     }
   1722 
   1723     private void playRecording(Uri recordingUri) {
   1724         mTvView.playRecording(recordingUri, mOnTuneListener);
   1725         mOnTuneListener.onPlayRecording();
   1726         updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE);
   1727     }
   1728 
   1729     private void tune() {
   1730         if (DEBUG) Log.d(TAG, "tune()");
   1731         mTuneDurationTimer.start();
   1732 
   1733         lazyInitializeIfNeeded(LAZY_INITIALIZATION_DELAY);
   1734 
   1735         // Prerequisites to be able to tune.
   1736         if (mInputIdUnderSetup != null) {
   1737             mTunePending = true;
   1738             return;
   1739         }
   1740         mTunePending = false;
   1741         final Channel channel = mChannelTuner.getCurrentChannel();
   1742         if (!mChannelTuner.isCurrentChannelPassthrough()) {
   1743             if (mTvInputManagerHelper.getTunerTvInputSize() == 0) {
   1744                 Toast.makeText(this, R.string.msg_no_input, Toast.LENGTH_SHORT).show();
   1745                 // TODO: Direct the user to a Play Store landing page for TvInputService apps.
   1746                 finish();
   1747                 return;
   1748             }
   1749             SetupUtils setupUtils = SetupUtils.getInstance(this);
   1750             if (setupUtils.isFirstTune()) {
   1751                 if (!mChannelTuner.areAllChannelsLoaded()) {
   1752                     // tune() will be called, once all channels are loaded.
   1753                     stopTv("tune()", false);
   1754                     return;
   1755                 }
   1756                 if (mChannelDataManager.getChannelCount() > 0) {
   1757                     mOverlayManager.showIntroDialog();
   1758                 } else if (!Features.ONBOARDING_EXPERIENCE.isEnabled(this)) {
   1759                     mOverlayManager.showSetupFragment();
   1760                     return;
   1761                 }
   1762             }
   1763             if (!TvCommonUtils.isRunningInTest() && mShowNewSourcesFragment
   1764                     && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) {
   1765                 // Show new channel sources fragment.
   1766                 runAfterAttachedToWindow(new Runnable() {
   1767                     @Override
   1768                     public void run() {
   1769                         mOverlayManager.runAfterOverlaysAreClosed(new Runnable() {
   1770                             @Override
   1771                             public void run() {
   1772                                 mOverlayManager.showNewSourcesFragment();
   1773                             }
   1774                         });
   1775                     }
   1776                 });
   1777             }
   1778             mShowNewSourcesFragment = false;
   1779             if (mChannelTuner.getBrowsableChannelCount() == 0
   1780                     && mChannelDataManager.getChannelCount() > 0
   1781                     && !mOverlayManager.getSideFragmentManager().isActive()) {
   1782                 if (!mChannelTuner.areAllChannelsLoaded()) {
   1783                     return;
   1784                 }
   1785                 if (mTvInputManagerHelper.getTunerTvInputSize() == 1) {
   1786                     mOverlayManager.getSideFragmentManager().show(
   1787                             new CustomizeChannelListFragment());
   1788                 } else {
   1789                     showSettingsFragment();
   1790                 }
   1791                 return;
   1792             }
   1793             // TODO: need to refactor the following code to put in startTv.
   1794             if (channel == null) {
   1795                 // There is no channel to tune to.
   1796                 stopTv("tune()", false);
   1797                 if (!mChannelDataManager.isDbLoadFinished()) {
   1798                     // Wait until channel data is loaded in order to know the number of channels.
   1799                     // tune() will be retried, once the channel data is loaded.
   1800                     return;
   1801                 }
   1802                 if (mOverlayManager.getSideFragmentManager().isActive()) {
   1803                     return;
   1804                 }
   1805                 mOverlayManager.showSetupFragment();
   1806                 return;
   1807             }
   1808             setupUtils.onTuned();
   1809             if (mTuneParams != null) {
   1810                 Long initChannelId = mTuneParams.getLong(KEY_INIT_CHANNEL_ID);
   1811                 if (initChannelId == channel.getId()) {
   1812                     mTuneParams.remove(KEY_INIT_CHANNEL_ID);
   1813                 } else {
   1814                     mTuneParams = null;
   1815                 }
   1816             }
   1817         }
   1818 
   1819         mIsCurrentChannelUnblockedByUser = false;
   1820         if (!isUnderShrunkenTvView()) {
   1821             mLastAllowedRatingForCurrentChannel = null;
   1822         }
   1823         mHandler.removeMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE);
   1824         if (mAccessibilityManager.isEnabled()) {
   1825             // For every tune, we need to inform the tuned channel or input to a user,
   1826             // if Talkback is turned on.
   1827             AccessibilityEvent event = AccessibilityEvent.obtain();
   1828             event.setClassName(getClass().getName());
   1829             event.setPackageName(getPackageName());
   1830             event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
   1831             if (TvContract.isChannelUriForPassthroughInput(channel.getUri())) {
   1832                 TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(channel.getInputId());
   1833                 event.getText().add(Utils.loadLabel(this, input));
   1834             } else if (TextUtils.isEmpty(channel.getDisplayName())) {
   1835                 event.getText().add(channel.getDisplayNumber());
   1836             } else {
   1837                 event.getText().add(channel.getDisplayNumber() + " " + channel.getDisplayName());
   1838             }
   1839             mAccessibilityManager.sendAccessibilityEvent(event);
   1840         }
   1841 
   1842         boolean success = mTvView.tuneTo(channel, mTuneParams, mOnTuneListener);
   1843         mOnTuneListener.onTune(channel, isUnderShrunkenTvView());
   1844 
   1845         mTuneParams = null;
   1846         if (!success) {
   1847             Toast.makeText(this, R.string.msg_tune_failed, Toast.LENGTH_SHORT).show();
   1848             return;
   1849         }
   1850 
   1851         // Explicitly make the TV view main to make the selected input an HDMI-CEC active source.
   1852         mTvView.setMain();
   1853         scheduleRestoreMainTvView();
   1854         if (!isUnderShrunkenTvView()) {
   1855             if (!channel.isPassthrough()) {
   1856                 addToRecentChannels(channel.getId());
   1857             }
   1858             Utils.setLastWatchedChannel(this, channel);
   1859             TvApplication.getSingletons(this).getMainActivityWrapper()
   1860                     .notifyCurrentChannelChange(this, channel);
   1861         }
   1862         checkChannelLockNeeded(mTvView);
   1863         updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE);
   1864         if (mActivityResumed) {
   1865             // requestVisibleBehind should be called after onResume() is called. But, when
   1866             // launcher is over the TV app and the screen is turned off and on, tune() can
   1867             // be called during the pause state by mBroadcastReceiver (Intent.ACTION_SCREEN_ON).
   1868             requestVisibleBehind(true);
   1869         }
   1870         updateMediaSession();
   1871     }
   1872 
   1873     private void runAfterAttachedToWindow(final Runnable runnable) {
   1874         if (mOverlayRootView.isLaidOut()) {
   1875             runnable.run();
   1876         } else {
   1877             mOverlayRootView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
   1878                 @Override
   1879                 public void onViewAttachedToWindow(View v) {
   1880                     mOverlayRootView.removeOnAttachStateChangeListener(this);
   1881                     runnable.run();
   1882                 }
   1883 
   1884                 @Override
   1885                 public void onViewDetachedFromWindow(View v) { }
   1886             });
   1887         }
   1888     }
   1889 
   1890     private void updateMediaSession() {
   1891         if (getCurrentChannel() == null) {
   1892             mMediaSession.setActive(false);
   1893             return;
   1894         }
   1895 
   1896         // If the channel is blocked, display a lock and a short text on the Now Playing Card
   1897         if (mTvView.isScreenBlocked() || mTvView.getBlockedContentRating() != null) {
   1898             setMediaSessionPlaybackState(false);
   1899 
   1900             Bitmap art = BitmapFactory.decodeResource(
   1901                     getResources(), R.drawable.ic_message_lock_preview);
   1902             updateMediaMetadata(
   1903                     getResources().getString(R.string.channel_banner_locked_channel_title), art);
   1904             mMediaSession.setActive(true);
   1905             return;
   1906         }
   1907 
   1908         final Program program = getCurrentProgram();
   1909         String cardTitleText = program == null ? null : program.getTitle();
   1910         if (TextUtils.isEmpty(cardTitleText)) {
   1911             cardTitleText = getCurrentChannel().getDisplayName();
   1912         }
   1913         updateMediaMetadata(cardTitleText, null);
   1914         setMediaSessionPlaybackState(true);
   1915 
   1916         if (program != null && program.getPosterArtUri() != null) {
   1917             program.loadPosterArt(MainActivity.this, mNowPlayingCardWidth, mNowPlayingCardHeight,
   1918                     createProgramPosterArtCallback(MainActivity.this, program));
   1919         } else {
   1920             updateMediaMetadataWithAlternativeArt(program);
   1921         }
   1922 
   1923         mMediaSession.setActive(true);
   1924     }
   1925 
   1926     private static ImageLoader.ImageLoaderCallback<MainActivity> createProgramPosterArtCallback(
   1927             MainActivity mainActivity, final Program program) {
   1928         return new ImageLoader.ImageLoaderCallback<MainActivity>(mainActivity) {
   1929             @Override
   1930             public void onBitmapLoaded(MainActivity mainActivity, @Nullable Bitmap posterArt) {
   1931                 if (program != mainActivity.getCurrentProgram()
   1932                         || mainActivity.getCurrentChannel() == null) {
   1933                     return;
   1934                 }
   1935                 mainActivity.updateProgramPosterArt(program, posterArt);
   1936             }
   1937         };
   1938     }
   1939 
   1940     private void updateProgramPosterArt(Program program, @Nullable Bitmap posterArt) {
   1941         if (getCurrentChannel() == null) {
   1942             return;
   1943         }
   1944         if (posterArt != null) {
   1945             String cardTitleText = program == null ? null : program.getTitle();
   1946             if (TextUtils.isEmpty(cardTitleText)) {
   1947                 cardTitleText = getCurrentChannel().getDisplayName();
   1948             }
   1949             updateMediaMetadata(cardTitleText, posterArt);
   1950         } else {
   1951             updateMediaMetadataWithAlternativeArt(program);
   1952         }
   1953     }
   1954 
   1955     private void updateMediaMetadata(String title, Bitmap posterArt) {
   1956         MediaMetadata.Builder builder = new MediaMetadata.Builder();
   1957         builder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
   1958         if (posterArt != null) {
   1959             builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt);
   1960         }
   1961         mMediaSession.setMetadata(builder.build());
   1962     }
   1963 
   1964     private void updateMediaMetadataWithAlternativeArt(final Program program) {
   1965         Channel channel = getCurrentChannel();
   1966         if (channel == null || program != getCurrentProgram()) {
   1967             return;
   1968         }
   1969 
   1970         String cardTitleText;
   1971         if (channel.isPassthrough()) {
   1972             TvInputInfo input = getTvInputManagerHelper().getTvInputInfo(channel.getInputId());
   1973             cardTitleText = Utils.loadLabel(this, input);
   1974         } else {
   1975             cardTitleText = program == null ? null : program.getTitle();
   1976             if (TextUtils.isEmpty(cardTitleText)) {
   1977                 cardTitleText = channel.getDisplayName();
   1978             }
   1979         }
   1980 
   1981         Bitmap posterArt = BitmapFactory.decodeResource(
   1982                 getResources(), R.drawable.default_now_card);
   1983         updateMediaMetadata(cardTitleText, posterArt);
   1984     }
   1985 
   1986     private void setMediaSessionPlaybackState(boolean isPlaying) {
   1987         PlaybackState.Builder builder = new PlaybackState.Builder();
   1988         builder.setState(isPlaying ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_STOPPED,
   1989                 PlaybackState.PLAYBACK_POSITION_UNKNOWN,
   1990                 isPlaying ? MEDIA_SESSION_PLAYING_SPEED : MEDIA_SESSION_STOPPED_SPEED);
   1991         mMediaSession.setPlaybackState(builder.build());
   1992     }
   1993 
   1994     private void addToRecentChannels(long channelId) {
   1995         if (!mRecentChannels.remove(channelId)) {
   1996             if (mRecentChannels.size() >= MAX_RECENT_CHANNELS) {
   1997                 mRecentChannels.removeLast();
   1998             }
   1999         }
   2000         mRecentChannels.addFirst(channelId);
   2001         mOverlayManager.getMenu().onRecentChannelsChanged();
   2002     }
   2003 
   2004     /**
   2005      * Returns the recently tuned channels.
   2006      */
   2007     public ArrayDeque<Long> getRecentChannels() {
   2008         return mRecentChannels;
   2009     }
   2010 
   2011     private void checkChannelLockNeeded(TunableTvView tvView) {
   2012         Channel channel = tvView.getCurrentChannel();
   2013         if (tvView.isPlaying() && channel != null) {
   2014             if (getParentalControlSettings().isParentalControlsEnabled()
   2015                     && channel.isLocked()
   2016                     && !mShowLockedChannelsTemporarily
   2017                     && !(isUnderShrunkenTvView()
   2018                             && channel.equals(mChannelBeforeShrunkenTvView)
   2019                             && mWasChannelUnblockedBeforeShrunkenByUser)) {
   2020                 if (DEBUG) Log.d(TAG, "Channel " + channel.getId() + " is locked");
   2021                 blockScreen(tvView);
   2022             } else {
   2023                 unblockScreen(tvView);
   2024             }
   2025         }
   2026     }
   2027 
   2028     private void blockScreen(TunableTvView tvView) {
   2029         tvView.blockScreen();
   2030         if (tvView == mTvView) {
   2031             updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
   2032             updateMediaSession();
   2033         }
   2034     }
   2035 
   2036     private void unblockScreen(TunableTvView tvView) {
   2037         tvView.unblockScreen();
   2038         if (tvView == mTvView) {
   2039             updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
   2040             updateMediaSession();
   2041         }
   2042     }
   2043 
   2044     /**
   2045      * Shows the channel banner if it was hidden from the side fragment.
   2046      *
   2047      * <p>When the side fragment is visible, showing the channel banner should be put off until the
   2048      * side fragment is closed even though the channel changes.
   2049      */
   2050     public void showChannelBannerIfHiddenBySideFragment() {
   2051         if (mChannelBannerHiddenBySideFragment) {
   2052             updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
   2053         }
   2054     }
   2055 
   2056     private void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) {
   2057         if(DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")");
   2058         if (!mChannelTuner.isCurrentChannelPassthrough() || mTvView.isRecordingPlayback()) {
   2059             int lockType = ChannelBannerView.LOCK_NONE;
   2060             if (mTvView.isScreenBlocked()) {
   2061                 lockType = ChannelBannerView.LOCK_CHANNEL_INFO;
   2062             } else if (mTvView.getBlockedContentRating() != null
   2063                     || (getParentalControlSettings().isParentalControlsEnabled()
   2064                             && !mTvView.isVideoAvailable())) {
   2065                 // If the parental control is enabled, do not show the program detail until the
   2066                 // video becomes available.
   2067                 lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
   2068             }
   2069             if (lockType == ChannelBannerView.LOCK_NONE) {
   2070                 if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) {
   2071                     // Do not show detailed program information while fast-tuning.
   2072                     lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
   2073                 } else if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
   2074                         && getParentalControlSettings().isParentalControlsEnabled()) {
   2075                     // If parental control is turned on,
   2076                     // assumes that program is locked by default and waits for onContentAllowed.
   2077                     lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
   2078                 }
   2079             }
   2080             // If lock type is not changed, we don't need to update channel banner by parental
   2081             // control.
   2082             if (!mChannelBannerView.setLockType(lockType)
   2083                     && reason == UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK) {
   2084                 return;
   2085             }
   2086 
   2087             mChannelBannerView.updateViews(mTvView);
   2088         }
   2089         boolean needToShowBanner = (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW
   2090                 || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
   2091                 || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST);
   2092         boolean noOverlayUiWhenResume =
   2093                 mInputToSetUp == null && !mShowProgramGuide && !mShowSelectInputView;
   2094         if (needToShowBanner && noOverlayUiWhenResume
   2095                 && mOverlayManager.getCurrentDialog() == null
   2096                 && !mOverlayManager.isSetupFragmentActive()
   2097                 && !mOverlayManager.isNewSourcesFragmentActive()) {
   2098             if (mChannelTuner.getCurrentChannel() == null) {
   2099                 mChannelBannerHiddenBySideFragment = false;
   2100             } else if (mOverlayManager.getSideFragmentManager().isActive()) {
   2101                 mChannelBannerHiddenBySideFragment = true;
   2102             } else {
   2103                 mChannelBannerHiddenBySideFragment = false;
   2104                 mOverlayManager.showBanner();
   2105             }
   2106         }
   2107         updateAvailabilityToast();
   2108     }
   2109 
   2110     /**
   2111      * Hide the overlays when tuning to a channel from the menu (e.g. Channels).
   2112      */
   2113     public void hideOverlaysForTune() {
   2114         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE);
   2115     }
   2116 
   2117     public boolean needToKeepSetupScreenWhenHidingOverlay() {
   2118         return mInputIdUnderSetup != null && mIsSetupActivityCalledByPopup;
   2119     }
   2120 
   2121     // For now, this only takes care of 24fps.
   2122     private void applyDisplayRefreshRate(float videoFrameRate) {
   2123         boolean is24Fps = Math.abs(videoFrameRate - FRAME_RATE_FOR_FILM) < FRAME_RATE_EPSILON;
   2124         if (mIsFilmModeSet && !is24Fps) {
   2125             setPreferredRefreshRate(mDefaultRefreshRate);
   2126             mIsFilmModeSet = false;
   2127         } else if (!mIsFilmModeSet && is24Fps) {
   2128             DisplayManager displayManager = (DisplayManager) getSystemService(
   2129                     Context.DISPLAY_SERVICE);
   2130             Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
   2131 
   2132             float[] refreshRates = display.getSupportedRefreshRates();
   2133             for (float refreshRate : refreshRates) {
   2134                 // Be conservative and set only when the display refresh rate supports 24fps.
   2135                 if (Math.abs(videoFrameRate - refreshRate) < REFRESH_RATE_EPSILON) {
   2136                     setPreferredRefreshRate(refreshRate);
   2137                     mIsFilmModeSet = true;
   2138                     return;
   2139                 }
   2140             }
   2141         }
   2142     }
   2143 
   2144     private void setPreferredRefreshRate(float refreshRate) {
   2145         Window window = getWindow();
   2146         WindowManager.LayoutParams layoutParams = window.getAttributes();
   2147         layoutParams.preferredRefreshRate = refreshRate;
   2148         window.setAttributes(layoutParams);
   2149     }
   2150 
   2151     private void applyMultiAudio() {
   2152         List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO);
   2153         if (tracks == null) {
   2154             mTvOptionsManager.onMultiAudioChanged(null);
   2155             return;
   2156         }
   2157 
   2158         String id = TvSettings.getMultiAudioId(this);
   2159         String language = TvSettings.getMultiAudioLanguage(this);
   2160         int channelCount = TvSettings.getMultiAudioChannelCount(this);
   2161         TvTrackInfo bestTrack = TvTrackInfoUtils
   2162                 .getBestTrackInfo(tracks, id, language, channelCount);
   2163         if (bestTrack != null) {
   2164             String selectedTrack = getSelectedTrack(TvTrackInfo.TYPE_AUDIO);
   2165             if (!bestTrack.getId().equals(selectedTrack)) {
   2166                 selectTrack(TvTrackInfo.TYPE_AUDIO, bestTrack);
   2167             } else {
   2168                 mTvOptionsManager.onMultiAudioChanged(
   2169                         Utils.getMultiAudioString(this, bestTrack, false));
   2170             }
   2171             return;
   2172         }
   2173         mTvOptionsManager.onMultiAudioChanged(null);
   2174     }
   2175 
   2176     private void applyClosedCaption() {
   2177         List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE);
   2178         if (tracks == null) {
   2179             mTvOptionsManager.onClosedCaptionsChanged(null);
   2180             return;
   2181         }
   2182 
   2183         boolean enabled = mCaptionSettings.isEnabled();
   2184         mTvView.setClosedCaptionEnabled(enabled);
   2185 
   2186         String selectedTrackId = getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE);
   2187         TvTrackInfo alternativeTrack = null;
   2188         if (enabled) {
   2189             String language = mCaptionSettings.getLanguage();
   2190             String trackId = mCaptionSettings.getTrackId();
   2191             for (TvTrackInfo track : tracks) {
   2192                 if (Utils.isEqualLanguage(track.getLanguage(), language)) {
   2193                     if (track.getId().equals(trackId)) {
   2194                         if (!track.getId().equals(selectedTrackId)) {
   2195                             selectTrack(TvTrackInfo.TYPE_SUBTITLE, track);
   2196                         } else {
   2197                             // Already selected. Update the option string only.
   2198                             mTvOptionsManager.onClosedCaptionsChanged(track);
   2199                         }
   2200                         if (DEBUG) {
   2201                             Log.d(TAG, "Subtitle Track Selected {id=" + track.getId()
   2202                                     + ", language=" + track.getLanguage() + "}");
   2203                         }
   2204                         return;
   2205                     } else if (alternativeTrack == null) {
   2206                         alternativeTrack = track;
   2207                     }
   2208                 }
   2209             }
   2210             if (alternativeTrack != null) {
   2211                 if (!alternativeTrack.getId().equals(selectedTrackId)) {
   2212                     selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack);
   2213                 } else {
   2214                     mTvOptionsManager.onClosedCaptionsChanged(alternativeTrack);
   2215                 }
   2216                 if (DEBUG) {
   2217                     Log.d(TAG, "Subtitle Track Selected {id=" + alternativeTrack.getId()
   2218                             + ", language=" + alternativeTrack.getLanguage() + "}");
   2219                 }
   2220                 return;
   2221             }
   2222         }
   2223         if (selectedTrackId != null) {
   2224             selectTrack(TvTrackInfo.TYPE_SUBTITLE, null);
   2225             if (DEBUG) Log.d(TAG, "Subtitle Track Unselected");
   2226             return;
   2227         }
   2228         mTvOptionsManager.onClosedCaptionsChanged(null);
   2229     }
   2230 
   2231     /**
   2232      * Pops up the KeypadChannelSwitchView with the given key input event.
   2233      *
   2234      * @param keyCode A key code of the key event.
   2235      */
   2236     public void showKeypadChannelSwitchView(int keyCode) {
   2237         if (mChannelTuner.areAllChannelsLoaded()) {
   2238             mOverlayManager.showKeypadChannelSwitch();
   2239             mKeypadChannelSwitchView.onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0);
   2240         }
   2241     }
   2242 
   2243     public void showSearchActivity() {
   2244         // HACK: Once we moved the window layer to TYPE_APPLICATION_SUB_PANEL,
   2245         // the voice button doesn't work. So we directly call the voice action.
   2246         SearchManagerHelper.getInstance(this).launchAssistAction();
   2247     }
   2248 
   2249     public void showProgramGuideSearchFragment() {
   2250         getFragmentManager().beginTransaction().replace(R.id.fragment_container, mSearchFragment)
   2251                 .addToBackStack(null).commit();
   2252     }
   2253 
   2254     @Override
   2255     protected void onSaveInstanceState(Bundle outState) {
   2256         // Do not save instance state because restoring instance state when TV app died
   2257         // unexpectedly can cause some problems like initializing fragments duplicately and
   2258         // accessing resource before it is initialized.
   2259     }
   2260 
   2261     @Override
   2262     protected void onDestroy() {
   2263         if (DEBUG) Log.d(TAG, "onDestroy()");
   2264         if (mChannelTuner != null) {
   2265             mChannelTuner.removeListener(mChannelTunerListener);
   2266             mChannelTuner.stop();
   2267         }
   2268         TvApplication application = ((TvApplication) getApplication());
   2269         if (mProgramDataManager != null) {
   2270             mProgramDataManager.removeOnCurrentProgramUpdatedListener(
   2271                     Channel.INVALID_ID, mOnCurrentProgramUpdatedListener);
   2272             if (application.getMainActivityWrapper().isCurrent(this)) {
   2273                 mProgramDataManager.setPrefetchEnabled(false);
   2274             }
   2275         }
   2276         if (mPipInputManager != null) {
   2277             mPipInputManager.stop();
   2278         }
   2279         if (mOverlayManager != null) {
   2280             mOverlayManager.release();
   2281         }
   2282         if (mKeypadChannelSwitchView != null) {
   2283             mKeypadChannelSwitchView.setChannels(null);
   2284         }
   2285         mMemoryManageables.clear();
   2286         if (mMediaSession != null) {
   2287             mMediaSession.release();
   2288         }
   2289         if (mAudioCapabilitiesReceiver != null) {
   2290             mAudioCapabilitiesReceiver.unregister();
   2291         }
   2292         mHandler.removeCallbacksAndMessages(null);
   2293         application.getMainActivityWrapper().onMainActivityDestroyed(this);
   2294         if (mSendConfigInfoRecurringRunner != null) {
   2295             mSendConfigInfoRecurringRunner.stop();
   2296             mSendConfigInfoRecurringRunner = null;
   2297         }
   2298         if (mChannelStatusRecurringRunner != null) {
   2299             mChannelStatusRecurringRunner.stop();
   2300             mChannelStatusRecurringRunner = null;
   2301         }
   2302         if (mTvInputManagerHelper != null) {
   2303             mTvInputManagerHelper.removeCallback(mTvInputCallback);
   2304         }
   2305         super.onDestroy();
   2306     }
   2307 
   2308     @Override
   2309     public boolean onKeyDown(int keyCode, KeyEvent event) {
   2310         if (SystemProperties.LOG_KEYEVENT.getValue()) {
   2311             Log.d(TAG, "onKeyDown(" + keyCode + ", " + event + ")");
   2312         }
   2313         switch (mOverlayManager.onKeyDown(keyCode, event)) {
   2314             case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY:
   2315                 return super.onKeyDown(keyCode, event);
   2316             case KEY_EVENT_HANDLER_RESULT_HANDLED:
   2317                 return true;
   2318             case KEY_EVENT_HANDLER_RESULT_NOT_HANDLED:
   2319                 return false;
   2320             case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH:
   2321             default:
   2322                 // pass through
   2323         }
   2324         if (mSearchFragment.isVisible()) {
   2325             return super.onKeyDown(keyCode, event);
   2326         }
   2327         if (!mChannelTuner.areAllChannelsLoaded()) {
   2328             return false;
   2329         }
   2330         if (!mChannelTuner.isCurrentChannelPassthrough()) {
   2331             switch (keyCode) {
   2332                 case KeyEvent.KEYCODE_CHANNEL_UP:
   2333                 case KeyEvent.KEYCODE_DPAD_UP:
   2334                     if (event.getRepeatCount() == 0
   2335                             && mChannelTuner.getBrowsableChannelCount() > 0) {
   2336                         moveToAdjacentChannel(true, false);
   2337                         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_UP_PRESSED,
   2338                                 System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
   2339                         mTracker.sendChannelUp();
   2340                     }
   2341                     return true;
   2342                 case KeyEvent.KEYCODE_CHANNEL_DOWN:
   2343                 case KeyEvent.KEYCODE_DPAD_DOWN:
   2344                     if (event.getRepeatCount() == 0
   2345                             && mChannelTuner.getBrowsableChannelCount() > 0) {
   2346                         moveToAdjacentChannel(false, false);
   2347                         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_DOWN_PRESSED,
   2348                                 System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
   2349                         mTracker.sendChannelDown();
   2350                     }
   2351                     return true;
   2352             }
   2353         }
   2354         return super.onKeyDown(keyCode, event);
   2355     }
   2356 
   2357     @Override
   2358     public boolean onKeyUp(int keyCode, KeyEvent event) {
   2359         /*
   2360          * The following keyboard keys map to these remote keys or "debug actions"
   2361          *  - --------
   2362          *  A KEYCODE_MEDIA_AUDIO_TRACK
   2363          *  D debug: show debug options
   2364          *  E updateChannelBannerAndShowIfNeeded
   2365          *  I KEYCODE_TV_INPUT
   2366          *  O debug: show display mode option
   2367          *  P debug: togglePipView
   2368          *  S KEYCODE_CAPTIONS: select subtitle
   2369          *  W debug: toggle screen size
   2370          *  V KEYCODE_MEDIA_RECORD debug: record the current channel for 30 sec
   2371          *  X KEYCODE_BUTTON_X KEYCODE_PROG_BLUE debug: record current channel for a few minutes
   2372          *  Y KEYCODE_BUTTON_Y KEYCODE_PROG_GREEN debug: Play a recording
   2373          */
   2374         if (SystemProperties.LOG_KEYEVENT.getValue()) {
   2375             Log.d(TAG, "onKeyUp(" + keyCode + ", " + event + ")");
   2376         }
   2377         // If we are in the middle of channel change, finish it before showing overlays.
   2378         finishChannelChangeIfNeeded();
   2379 
   2380         if (event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) {
   2381             showSearchActivity();
   2382             return true;
   2383         }
   2384         switch (mOverlayManager.onKeyUp(keyCode, event)) {
   2385             case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY:
   2386                 return super.onKeyUp(keyCode, event);
   2387             case KEY_EVENT_HANDLER_RESULT_HANDLED:
   2388                 return true;
   2389             case KEY_EVENT_HANDLER_RESULT_NOT_HANDLED:
   2390                 return false;
   2391             case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH:
   2392             default:
   2393                 // pass through
   2394         }
   2395         if (mSearchFragment.isVisible()) {
   2396             if (keyCode == KeyEvent.KEYCODE_BACK) {
   2397                 getFragmentManager().popBackStack();
   2398                 return true;
   2399             }
   2400             return super.onKeyUp(keyCode, event);
   2401         }
   2402         if (keyCode == KeyEvent.KEYCODE_BACK) {
   2403             // When the event is from onUnhandledInputEvent, onBackPressed is not automatically
   2404             // called. Therefore, we need to explicitly call onBackPressed().
   2405             onBackPressed();
   2406             return true;
   2407         }
   2408 
   2409         if (!mChannelTuner.areAllChannelsLoaded()) {
   2410             // Now channel map is under loading.
   2411         } else if (mChannelTuner.getBrowsableChannelCount() == 0) {
   2412             switch (keyCode) {
   2413                 case KeyEvent.KEYCODE_CHANNEL_UP:
   2414                 case KeyEvent.KEYCODE_DPAD_UP:
   2415                 case KeyEvent.KEYCODE_CHANNEL_DOWN:
   2416                 case KeyEvent.KEYCODE_DPAD_DOWN:
   2417                 case KeyEvent.KEYCODE_NUMPAD_ENTER:
   2418                 case KeyEvent.KEYCODE_DPAD_CENTER:
   2419                 case KeyEvent.KEYCODE_E:
   2420                 case KeyEvent.KEYCODE_MENU:
   2421                     showSettingsFragment();
   2422                     return true;
   2423             }
   2424         } else {
   2425             if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) {
   2426                 showKeypadChannelSwitchView(keyCode);
   2427                 return true;
   2428             }
   2429             switch (keyCode) {
   2430                 case KeyEvent.KEYCODE_DPAD_RIGHT:
   2431                     if (!PermissionUtils.hasModifyParentalControls(this)) {
   2432                         // TODO: support this feature for non-system LC app. b/23939816
   2433                         return true;
   2434                     }
   2435                     PinDialogFragment dialog = null;
   2436                     if (mTvView.isScreenBlocked()) {
   2437                         dialog = new PinDialogFragment(
   2438                                 PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL,
   2439                                 new PinDialogFragment.ResultListener() {
   2440                                     @Override
   2441                                     public void done(boolean success) {
   2442                                         if (success) {
   2443                                             unblockScreen(mTvView);
   2444                                             mIsCurrentChannelUnblockedByUser = true;
   2445                                         }
   2446                                     }
   2447                                 });
   2448                     } else if (mTvView.getBlockedContentRating() != null) {
   2449                         final TvContentRating rating = mTvView.getBlockedContentRating();
   2450                         dialog = new PinDialogFragment(
   2451                                 PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM,
   2452                                 new PinDialogFragment.ResultListener() {
   2453                                     @Override
   2454                                     public void done(boolean success) {
   2455                                         if (success) {
   2456                                             mLastAllowedRatingForCurrentChannel = rating;
   2457                                             mTvView.unblockContent(rating);
   2458                                         }
   2459                                     }
   2460                                 });
   2461                     }
   2462                     if (dialog != null) {
   2463                         mOverlayManager.showDialogFragment(PinDialogFragment.DIALOG_TAG, dialog,
   2464                                 false);
   2465                     }
   2466                     return true;
   2467 
   2468                 case KeyEvent.KEYCODE_ENTER:
   2469                 case KeyEvent.KEYCODE_NUMPAD_ENTER:
   2470                 case KeyEvent.KEYCODE_E:
   2471                 case KeyEvent.KEYCODE_DPAD_CENTER:
   2472                 case KeyEvent.KEYCODE_MENU:
   2473                     if (event.isCanceled()) {
   2474                         // Ignore canceled key.
   2475                         // Note that if there's a TIS granted RECEIVE_INPUT_EVENT,
   2476                         // fallback keys not blacklisted will have FLAG_CANCELED.
   2477                         // See dispatchKeyEvent() for detail.
   2478                         return true;
   2479                     }
   2480                     if (keyCode != KeyEvent.KEYCODE_MENU) {
   2481                         updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
   2482                     }
   2483                     if (keyCode != KeyEvent.KEYCODE_E) {
   2484                         mOverlayManager.showMenu(mTvView.isRecordingPlayback()
   2485                                 ? Menu.REASON_RECORDING_PLAYBACK : Menu.REASON_NONE);
   2486                     }
   2487                     return true;
   2488                 case KeyEvent.KEYCODE_CHANNEL_UP:
   2489                 case KeyEvent.KEYCODE_DPAD_UP:
   2490                 case KeyEvent.KEYCODE_CHANNEL_DOWN:
   2491                 case KeyEvent.KEYCODE_DPAD_DOWN:
   2492                     // Channel change is already done in the head of this method.
   2493                     return true;
   2494                 case KeyEvent.KEYCODE_S:
   2495                     if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
   2496                         break;
   2497                     }
   2498                 case KeyEvent.KEYCODE_CAPTIONS: {
   2499                     mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment());
   2500                     return true;
   2501                 }
   2502                 case KeyEvent.KEYCODE_A:
   2503                     if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
   2504                         break;
   2505                     }
   2506                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
   2507                     mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment());
   2508                     return true;
   2509                 }
   2510                 case KeyEvent.KEYCODE_GUIDE: {
   2511                     mOverlayManager.showProgramGuide();
   2512                     return true;
   2513                 }
   2514                 case KeyEvent.KEYCODE_INFO: {
   2515                     mOverlayManager.showBanner();
   2516                     return true;
   2517                 }
   2518             }
   2519         }
   2520         if (SystemProperties.USE_DEBUG_KEYS.getValue()) {
   2521             switch (keyCode) {
   2522                 case KeyEvent.KEYCODE_W: {
   2523                     mDebugNonFullSizeScreen = !mDebugNonFullSizeScreen;
   2524                     if (mDebugNonFullSizeScreen) {
   2525                         FrameLayout.LayoutParams params =
   2526                                 (FrameLayout.LayoutParams) mTvView.getLayoutParams();
   2527                         params.width = 960;
   2528                         params.height = 540;
   2529                         params.gravity = Gravity.START;
   2530                         mTvView.setLayoutParams(params);
   2531                     } else {
   2532                         FrameLayout.LayoutParams params =
   2533                                 (FrameLayout.LayoutParams) mTvView.getLayoutParams();
   2534                         params.width = ViewGroup.LayoutParams.MATCH_PARENT;
   2535                         params.height = ViewGroup.LayoutParams.MATCH_PARENT;
   2536                         params.gravity = Gravity.CENTER;
   2537                         mTvView.setLayoutParams(params);
   2538                     }
   2539                     return true;
   2540                 }
   2541                 case KeyEvent.KEYCODE_P: {
   2542                     togglePipView();
   2543                     return true;
   2544                 }
   2545                 case KeyEvent.KEYCODE_CTRL_LEFT:
   2546                 case KeyEvent.KEYCODE_CTRL_RIGHT: {
   2547                     mUseKeycodeBlacklist = !mUseKeycodeBlacklist;
   2548                     return true;
   2549                 }
   2550                 case KeyEvent.KEYCODE_O: {
   2551                     mOverlayManager.getSideFragmentManager().show(new DisplayModeFragment());
   2552                     return true;
   2553                 }
   2554 
   2555                 case KeyEvent.KEYCODE_D:
   2556                     mOverlayManager.getSideFragmentManager().show(new DebugOptionFragment());
   2557                     return true;
   2558 
   2559                 case KeyEvent.KEYCODE_MEDIA_RECORD: // TODO(DVR) handle with debug_keys set
   2560                 case KeyEvent.KEYCODE_V: {
   2561                     DvrManager dvrManager = TvApplication.getSingletons(this).getDvrManager();
   2562                     long startTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(5);
   2563                     long endTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(35);
   2564                     dvrManager.addSchedule(getCurrentChannel(), startTime, endTime);
   2565                     return true;
   2566                 }
   2567                 case KeyEvent.KEYCODE_PROG_BLUE:
   2568                 case KeyEvent.KEYCODE_BUTTON_X:
   2569                 case KeyEvent.KEYCODE_X: {
   2570                     if (CommonFeatures.DVR.isEnabled(this)) {
   2571                         Channel channel = mTvView.getCurrentChannel();
   2572                         long channelId = channel.getId();
   2573                         Program p = mProgramDataManager.getCurrentProgram(channelId);
   2574                         if (p == null) {
   2575                             long now = System.currentTimeMillis();
   2576                             mDvrManager
   2577                                     .addSchedule(channel, now, now + TimeUnit.MINUTES.toMillis(1));
   2578                         } else {
   2579                             mDvrManager.addSchedule(p,
   2580                                     mDvrManager.getScheduledRecordingsThatConflict(p));
   2581                         }
   2582                         return true;
   2583                     }
   2584                 }
   2585                 case KeyEvent.KEYCODE_PROG_YELLOW:
   2586                 case KeyEvent.KEYCODE_BUTTON_Y:
   2587                 case KeyEvent.KEYCODE_Y: {
   2588                     if (CommonFeatures.DVR.isEnabled(this) && BuildCompat.isAtLeastN()) {
   2589                         // TODO(DVR) only get finished recordings.
   2590                         List<RecordedProgram> recordedPrograms = mDvrDataManager
   2591                                 .getRecordedPrograms();
   2592                         Log.d(TAG, "Found " + recordedPrograms.size() + "  recordings");
   2593                         if (recordedPrograms.isEmpty()) {
   2594                             Toast.makeText(this, "No finished recording to play", Toast.LENGTH_LONG)
   2595                                     .show();
   2596                         } else {
   2597                             RecordedProgram r = recordedPrograms.get(0);
   2598                             Intent intent = new Intent(this, DvrPlayActivity.class);
   2599                             intent.putExtra(ScheduledRecording.RECORDING_ID_EXTRA, r.getId());
   2600                             startActivity(intent);
   2601                         }
   2602                         return true;
   2603                     }
   2604                 }
   2605             }
   2606         }
   2607         return super.onKeyUp(keyCode, event);
   2608     }
   2609 
   2610     @Override
   2611     public boolean onKeyLongPress(int keyCode, KeyEvent event) {
   2612         if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "onKeyLongPress(" + event);
   2613         if (USE_BACK_KEY_LONG_PRESS) {
   2614             // Treat the BACK key long press as the normal press since we changed the behavior in
   2615             // onBackPressed().
   2616             if (keyCode == KeyEvent.KEYCODE_BACK) {
   2617                 // It takes long time for TV app to finish, so stop TV first.
   2618                 stopAll(false);
   2619                 super.onBackPressed();
   2620                 return true;
   2621             }
   2622         }
   2623         return false;
   2624     }
   2625 
   2626     @Override
   2627     public void onBackPressed() {
   2628         // The activity should be returned to the caller of this activity
   2629         // when the mSource is not null.
   2630         if (!mOverlayManager.getSideFragmentManager().isActive() && isPlaying()
   2631                 && mSource == null) {
   2632             // If back key would exit TV app,
   2633             // show McLauncher instead so we can get benefit of McLauncher's shyMode.
   2634             Intent startMain = new Intent(Intent.ACTION_MAIN);
   2635             startMain.addCategory(Intent.CATEGORY_HOME);
   2636             startActivity(startMain);
   2637         } else {
   2638             super.onBackPressed();
   2639         }
   2640     }
   2641 
   2642     @Override
   2643     public void onUserInteraction() {
   2644         super.onUserInteraction();
   2645         if (mOverlayManager != null) {
   2646             mOverlayManager.onUserInteraction();
   2647         }
   2648     }
   2649 
   2650     @Override
   2651     public void enterPictureInPictureMode() {
   2652         // We need to hide overlay first, before moving the activity to PIP. If not, UI will
   2653         // be shown during PIP stack resizing, because UI and its animation is stuck during
   2654         // PIP resizing.
   2655         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
   2656         mHandler.post(new Runnable() {
   2657             @Override
   2658             public void run() {
   2659                 MainActivity.super.enterPictureInPictureMode();
   2660             }
   2661         });
   2662     }
   2663 
   2664     public void togglePipView() {
   2665         enablePipView(!mPipEnabled, true);
   2666         mOverlayManager.getMenu().update();
   2667     }
   2668 
   2669     public boolean isPipEnabled() {
   2670         return mPipEnabled;
   2671     }
   2672 
   2673     public void tuneToChannelForPip(Channel channel) {
   2674         if (!mPipEnabled) {
   2675             throw new IllegalStateException("tuneToChannelForPip is called when PIP is off");
   2676         }
   2677         if (mPipChannel.equals(channel)) {
   2678             return;
   2679         }
   2680         mPipChannel = channel;
   2681         startPip(true);
   2682     }
   2683 
   2684     public void enablePipView(boolean enable, boolean fromUserInteraction) {
   2685         if (enable == mPipEnabled) {
   2686             return;
   2687         }
   2688         if (enable) {
   2689             List<PipInput> pipAvailableInputs = mPipInputManager.getPipInputList(true);
   2690             if (pipAvailableInputs.isEmpty()) {
   2691                 Toast.makeText(this, R.string.msg_no_available_input_by_pip, Toast.LENGTH_SHORT)
   2692                         .show();
   2693                 return;
   2694             }
   2695             // TODO: choose the last pip input.
   2696             Channel pipChannel = pipAvailableInputs.get(0).getChannel();
   2697             if (pipChannel != null) {
   2698                 mPipEnabled = true;
   2699                 mPipChannel = pipChannel;
   2700                 startPip(fromUserInteraction);
   2701                 mTvViewUiManager.restorePipSize();
   2702                 mTvViewUiManager.restorePipLayout();
   2703                 mTvOptionsManager.onPipChanged(mPipEnabled);
   2704             } else {
   2705                 Toast.makeText(this, R.string.msg_no_available_input_by_pip, Toast.LENGTH_SHORT)
   2706                         .show();
   2707             }
   2708         } else {
   2709             mPipEnabled = false;
   2710             mPipChannel = null;
   2711             // Recover the stream volume of the main TV view, if needed.
   2712             if (mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW) {
   2713                 setVolumeByAudioFocusStatus(mTvView);
   2714                 mPipSound = TvSettings.PIP_SOUND_MAIN;
   2715                 mTvOptionsManager.onPipSoundChanged(mPipSound);
   2716             }
   2717             stopPip();
   2718             mTvViewUiManager.restoreDisplayMode(false);
   2719             mTvOptionsManager.onPipChanged(mPipEnabled);
   2720         }
   2721     }
   2722 
   2723     private boolean isChannelChangeKeyDownReceived() {
   2724         return mHandler.hasMessages(MSG_CHANNEL_UP_PRESSED)
   2725                 || mHandler.hasMessages(MSG_CHANNEL_DOWN_PRESSED);
   2726     }
   2727 
   2728     private void finishChannelChangeIfNeeded() {
   2729         if (!isChannelChangeKeyDownReceived()) {
   2730             return;
   2731         }
   2732         mHandler.removeMessages(MSG_CHANNEL_UP_PRESSED);
   2733         mHandler.removeMessages(MSG_CHANNEL_DOWN_PRESSED);
   2734         if (mChannelTuner.getBrowsableChannelCount() > 0) {
   2735             if (!mTvView.isPlaying()) {
   2736                 // We expect that mTvView is already played. But, it is sometimes not.
   2737                 // TODO: we figure out the reason when mTvView is not played.
   2738                 Log.w(TAG, "TV view isn't played in finishChannelChangeIfNeeded");
   2739             }
   2740             tuneToChannel(mChannelTuner.getCurrentChannel());
   2741         } else {
   2742             showSettingsFragment();
   2743         }
   2744     }
   2745 
   2746     private boolean dispatchKeyEventToSession(final KeyEvent event) {
   2747         if (SystemProperties.LOG_KEYEVENT.getValue()) {
   2748             Log.d(TAG, "dispatchKeyEventToSession(" + event + ")");
   2749         }
   2750         if (mPipEnabled && mChannelTuner.isCurrentChannelPassthrough()) {
   2751             // If PIP is enabled, key events will be used by UI.
   2752             return false;
   2753         }
   2754         boolean handled = false;
   2755         if (mTvView != null) {
   2756             handled = mTvView.dispatchKeyEvent(event);
   2757         }
   2758         if (isKeyEventBlocked()) {
   2759             if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK
   2760                     || event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B) && mNeedShowBackKeyGuide) {
   2761                 // KeyEvent.KEYCODE_BUTTON_B is also used like the back button.
   2762                 Toast.makeText(this, R.string.msg_back_key_guide, Toast.LENGTH_SHORT).show();
   2763                 mNeedShowBackKeyGuide = false;
   2764             }
   2765             return true;
   2766         }
   2767         return handled;
   2768     }
   2769 
   2770     private boolean isKeyEventBlocked() {
   2771         // If the current channel is passthrough channel without a PIP view,
   2772         // we always don't handle the key events in TV activity. Instead, the key event will
   2773         // be handled by the passthrough TV input.
   2774         return mChannelTuner.isCurrentChannelPassthrough() && !mPipEnabled;
   2775     }
   2776 
   2777     public void tuneToLastWatchedChannelForTunerInput() {
   2778         if (!mChannelTuner.isCurrentChannelPassthrough()) {
   2779             return;
   2780         }
   2781         if (mPipEnabled) {
   2782             if (!mPipChannel.isPassthrough()) {
   2783                 enablePipView(false, true);
   2784             }
   2785         }
   2786         stopTv();
   2787         startTv(null);
   2788     }
   2789 
   2790     public void tuneToChannel(Channel channel) {
   2791         if (channel == null) {
   2792             if (mTvView.isPlaying()) {
   2793                 mTvView.reset();
   2794             }
   2795         } else {
   2796             if (mPipEnabled && mPipInputManager.areInSamePipInput(channel, mPipChannel)) {
   2797                 enablePipView(false, true);
   2798             }
   2799             if (!mTvView.isPlaying()) {
   2800                 startTv(channel.getUri());
   2801             } else if (channel.equals(mTvView.getCurrentChannel())) {
   2802                 updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE);
   2803             } else if (mChannelTuner.moveToChannel(channel)) {
   2804                 // Channel banner would be updated inside of tune.
   2805                 tune();
   2806             } else {
   2807                 showSettingsFragment();
   2808             }
   2809         }
   2810     }
   2811 
   2812     /**
   2813      * This method just moves the channel in the channel map and updates the channel banner,
   2814      * but doesn't actually tune to the channel.
   2815      * The caller of this method should call {@link #tune} in the end.
   2816      *
   2817      * @param channelUp {@code true} for channel up, and {@code false} for channel down.
   2818      * @param fastTuning {@code true} if fast tuning is requested.
   2819      */
   2820     private void moveToAdjacentChannel(boolean channelUp, boolean fastTuning) {
   2821         if (mChannelTuner.moveToAdjacentBrowsableChannel(channelUp)) {
   2822             updateChannelBannerAndShowIfNeeded(fastTuning ? UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST
   2823                     : UPDATE_CHANNEL_BANNER_REASON_TUNE);
   2824         }
   2825     }
   2826 
   2827     public Channel getPipChannel() {
   2828         return mPipChannel;
   2829     }
   2830 
   2831     /**
   2832      * Swap the main and the sub screens while in the PIP mode.
   2833      */
   2834     public void swapPip() {
   2835         if (!mPipEnabled || mTvView == null || mPipView == null) {
   2836             Log.e(TAG, "swapPip() - not in PIP");
   2837             mPipSwap = false;
   2838             return;
   2839         }
   2840 
   2841         Channel channel = mTvView.getCurrentChannel();
   2842         boolean tvViewBlocked = mTvView.isScreenBlocked();
   2843         boolean pipViewBlocked = mPipView.isScreenBlocked();
   2844         if (channel == null || !mTvView.isPlaying()) {
   2845             // If the TV view is not currently playing or its current channel is null, swapping here
   2846             // basically means disabling the PIP mode and getting back to the full screen since
   2847             // there's no point of keeping a blank PIP screen at the bottom which is not tune-able.
   2848             enablePipView(false, true);
   2849             mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_DEFAULT);
   2850             mPipSwap = false;
   2851             return;
   2852         }
   2853 
   2854         // Reset the TV view and tune the PIP view to the previous channel of the TV view.
   2855         mTvView.reset();
   2856         mPipView.reset();
   2857         Channel oldPipChannel = mPipChannel;
   2858         tuneToChannelForPip(channel);
   2859         if (tvViewBlocked) {
   2860             mPipView.blockScreen();
   2861         } else {
   2862             mPipView.unblockScreen();
   2863         }
   2864 
   2865         if (oldPipChannel != null) {
   2866             // Tune the TV view to the previous PIP channel.
   2867             tuneToChannel(oldPipChannel);
   2868         }
   2869         if (pipViewBlocked) {
   2870             mTvView.blockScreen();
   2871         } else {
   2872             mTvView.unblockScreen();
   2873         }
   2874         if (mPipSound == TvSettings.PIP_SOUND_MAIN) {
   2875             setVolumeByAudioFocusStatus(mTvView);
   2876         } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW
   2877             setVolumeByAudioFocusStatus(mPipView);
   2878         }
   2879         mPipSwap = !mPipSwap;
   2880         mTvOptionsManager.onPipSwapChanged(mPipSwap);
   2881     }
   2882 
   2883     /**
   2884      * Toggle where the sound is coming from when the user is watching the PIP.
   2885      */
   2886     public void togglePipSoundMode() {
   2887         if (!mPipEnabled || mTvView == null || mPipView == null) {
   2888             Log.e(TAG, "togglePipSoundMode() - not in PIP");
   2889             return;
   2890         }
   2891         if (mPipSound == TvSettings.PIP_SOUND_MAIN) {
   2892             setVolumeByAudioFocusStatus(mPipView);
   2893             mPipSound = TvSettings.PIP_SOUND_PIP_WINDOW;
   2894         } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW
   2895             setVolumeByAudioFocusStatus(mTvView);
   2896             mPipSound = TvSettings.PIP_SOUND_MAIN;
   2897         }
   2898         restoreMainTvView();
   2899         mTvOptionsManager.onPipSoundChanged(mPipSound);
   2900     }
   2901 
   2902     /**
   2903      * Set the main TV view which holds HDMI-CEC active source based on the sound mode
   2904      */
   2905     private void restoreMainTvView() {
   2906         if (mPipSound == TvSettings.PIP_SOUND_MAIN) {
   2907             mTvView.setMain();
   2908         } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW
   2909             mPipView.setMain();
   2910         }
   2911     }
   2912 
   2913     @Override
   2914     public void onVisibleBehindCanceled() {
   2915         stopTv("onVisibleBehindCanceled()", false);
   2916         mTracker.sendScreenView("");
   2917         mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
   2918         mAudioManager.abandonAudioFocus(this);
   2919         if (mMediaSession.isActive()) {
   2920             mMediaSession.setActive(false);
   2921         }
   2922         stopPip();
   2923         mVisibleBehind = false;
   2924         if (!mOtherActivityLaunched && Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
   2925             // Workaround: in M, onStop is not called, even though it should be called after
   2926             // onVisibleBehindCanceled is called. As a workaround, we call finish().
   2927             finish();
   2928         }
   2929         super.onVisibleBehindCanceled();
   2930     }
   2931 
   2932     @Override
   2933     public void startActivity(Intent intent) {
   2934         mOtherActivityLaunched = true;
   2935         super.startActivity(intent);
   2936     }
   2937 
   2938     @Override
   2939     public void startActivityForResult(Intent intent, int requestCode) {
   2940         mOtherActivityLaunched = true;
   2941         super.startActivityForResult(intent, requestCode);
   2942     }
   2943 
   2944     public List<TvTrackInfo> getTracks(int type) {
   2945         return mTvView.getTracks(type);
   2946     }
   2947 
   2948     public String getSelectedTrack(int type) {
   2949         return mTvView.getSelectedTrack(type);
   2950     }
   2951 
   2952     public void selectTrack(int type, TvTrackInfo track) {
   2953         mTvView.selectTrack(type, track == null ? null : track.getId());
   2954         if (type == TvTrackInfo.TYPE_AUDIO) {
   2955             mTvOptionsManager.onMultiAudioChanged(track == null ? null :
   2956                     Utils.getMultiAudioString(this, track, false));
   2957         } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
   2958             mTvOptionsManager.onClosedCaptionsChanged(track);
   2959         }
   2960     }
   2961 
   2962     public void selectAudioTrack(String trackId) {
   2963         saveMultiAudioSetting(trackId);
   2964         applyMultiAudio();
   2965     }
   2966 
   2967     private void saveMultiAudioSetting(String trackId) {
   2968         List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO);
   2969         if (tracks != null) {
   2970             for (TvTrackInfo track : tracks) {
   2971                 if (track.getId().equals(trackId)) {
   2972                     TvSettings.setMultiAudioId(this, track.getId());
   2973                     TvSettings.setMultiAudioLanguage(this, track.getLanguage());
   2974                     TvSettings.setMultiAudioChannelCount(this, track.getAudioChannelCount());
   2975                     return;
   2976                 }
   2977             }
   2978         }
   2979         TvSettings.setMultiAudioId(this, null);
   2980         TvSettings.setMultiAudioLanguage(this, null);
   2981         TvSettings.setMultiAudioChannelCount(this, 0);
   2982     }
   2983 
   2984     public void selectSubtitleTrack(int option, String trackId) {
   2985         saveClosedCaptionSetting(option, trackId);
   2986         applyClosedCaption();
   2987     }
   2988 
   2989     public void selectSubtitleLanguage(int option, String language, String trackId) {
   2990         mCaptionSettings.setEnableOption(option);
   2991         mCaptionSettings.setLanguage(language);
   2992         mCaptionSettings.setTrackId(trackId);
   2993         applyClosedCaption();
   2994     }
   2995 
   2996     private void saveClosedCaptionSetting(int option, String trackId) {
   2997         mCaptionSettings.setEnableOption(option);
   2998         if (option == CaptionSettings.OPTION_ON) {
   2999             List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE);
   3000             if (tracks != null) {
   3001                 for (TvTrackInfo track : tracks) {
   3002                     if (track.getId().equals(trackId)) {
   3003                         mCaptionSettings.setLanguage(track.getLanguage());
   3004                         mCaptionSettings.setTrackId(trackId);
   3005                         return;
   3006                     }
   3007                 }
   3008             }
   3009         }
   3010     }
   3011 
   3012     private void updateAvailabilityToast() {
   3013         updateAvailabilityToast(mTvView);
   3014     }
   3015 
   3016     private void updateAvailabilityToast(StreamInfo info) {
   3017         if (info.isVideoAvailable()) {
   3018             return;
   3019         }
   3020 
   3021         int stringId;
   3022         switch (info.getVideoUnavailableReason()) {
   3023             case TunableTvView.VIDEO_UNAVAILABLE_REASON_NOT_TUNED:
   3024             case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
   3025             case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
   3026             case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:
   3027             case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
   3028                 return;
   3029             case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
   3030             default:
   3031                 stringId = R.string.msg_channel_unavailable_unknown;
   3032                 break;
   3033         }
   3034 
   3035         Toast.makeText(this, stringId, Toast.LENGTH_SHORT).show();
   3036     }
   3037 
   3038     public ParentalControlSettings getParentalControlSettings() {
   3039         return mTvInputManagerHelper.getParentalControlSettings();
   3040     }
   3041 
   3042     /**
   3043      * Returns a ContentRatingsManager instance.
   3044      */
   3045     public ContentRatingsManager getContentRatingsManager() {
   3046         return mTvInputManagerHelper.getContentRatingsManager();
   3047     }
   3048 
   3049     public CaptionSettings getCaptionSettings() {
   3050         return mCaptionSettings;
   3051     }
   3052 
   3053     // Initialize TV app for test. The setup process should be finished before the Live TV app is
   3054     // started. We only enable all the channels here.
   3055     private void initForTest() {
   3056         if (!TvCommonUtils.isRunningInTest()) {
   3057             return;
   3058         }
   3059 
   3060         Utils.enableAllChannels(this);
   3061     }
   3062 
   3063     // Lazy initialization
   3064     private void lazyInitializeIfNeeded(long delay) {
   3065         // Already initialized.
   3066         if (mLazyInitialized) {
   3067             return;
   3068         }
   3069         mLazyInitialized = true;
   3070         // Running initialization.
   3071         mHandler.postDelayed(new Runnable() {
   3072             @Override
   3073             public void run() {
   3074                 initAnimations();
   3075                 initSideFragments();
   3076             }
   3077         }, delay);
   3078     }
   3079 
   3080     private void initAnimations() {
   3081         mTvViewUiManager.initAnimatorIfNeeded();
   3082         mOverlayManager.initAnimatorIfNeeded();
   3083     }
   3084 
   3085     private void initSideFragments() {
   3086         SideFragment.preloadRecycledViews(this);
   3087     }
   3088 
   3089     @Override
   3090     public void onTrimMemory(int level) {
   3091         super.onTrimMemory(level);
   3092         for (MemoryManageable memoryManageable : mMemoryManageables) {
   3093             memoryManageable.performTrimMemory(level);
   3094         }
   3095     }
   3096 
   3097     private static class MainActivityHandler extends WeakHandler<MainActivity> {
   3098         MainActivityHandler(MainActivity mainActivity) {
   3099             super(mainActivity);
   3100         }
   3101 
   3102         @Override
   3103         protected void handleMessage(Message msg, @NonNull MainActivity mainActivity) {
   3104             switch (msg.what) {
   3105                 case MSG_CHANNEL_DOWN_PRESSED:
   3106                     long startTime = (Long) msg.obj;
   3107                     mainActivity.moveToAdjacentChannel(false, true);
   3108                     sendMessageDelayed(Message.obtain(msg), getDelay(startTime));
   3109                     break;
   3110                 case MSG_CHANNEL_UP_PRESSED:
   3111                     startTime = (Long) msg.obj;
   3112                     mainActivity.moveToAdjacentChannel(true, true);
   3113                     sendMessageDelayed(Message.obtain(msg), getDelay(startTime));
   3114                     break;
   3115                 case MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE:
   3116                     mainActivity.updateChannelBannerAndShowIfNeeded(
   3117                             UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
   3118                     break;
   3119             }
   3120         }
   3121 
   3122         private long getDelay(long startTime) {
   3123             if (System.currentTimeMillis() - startTime > CHANNEL_CHANGE_NORMAL_SPEED_DURATION_MS) {
   3124                 return CHANNEL_CHANGE_DELAY_MS_IN_MAX_SPEED;
   3125             }
   3126             return CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED;
   3127         }
   3128     }
   3129 
   3130     private class MyOnTuneListener implements OnTuneListener {
   3131         boolean mUnlockAllowedRatingBeforeShrunken = true;
   3132         boolean mWasUnderShrunkenTvView;
   3133         long mStreamInfoUpdateTimeThresholdMs;
   3134         Channel mChannel;
   3135 
   3136         public MyOnTuneListener() { }
   3137 
   3138         private void onTune(Channel channel, boolean wasUnderShrukenTvView) {
   3139             mStreamInfoUpdateTimeThresholdMs =
   3140                     System.currentTimeMillis() + FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS;
   3141             mChannel = channel;
   3142             mWasUnderShrunkenTvView = wasUnderShrukenTvView;
   3143         }
   3144 
   3145         private void onPlayRecording() {
   3146             mStreamInfoUpdateTimeThresholdMs =
   3147                     System.currentTimeMillis() + FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS;
   3148             mChannel = null;
   3149             mWasUnderShrunkenTvView = false;
   3150         }
   3151 
   3152         @Override
   3153         public void onUnexpectedStop(Channel channel) {
   3154             stopTv();
   3155             startTv(null);
   3156         }
   3157 
   3158         @Override
   3159         public void onTuneFailed(Channel channel) {
   3160             Log.w(TAG, "Failed to tune to channel " + channel.getId()
   3161                     + "@" + channel.getInputId());
   3162             if (mTvView.isFadedOut()) {
   3163                 mTvView.removeFadeEffect();
   3164             }
   3165             // TODO: show something to user about this error.
   3166         }
   3167 
   3168         @Override
   3169         public void onStreamInfoChanged(StreamInfo info) {
   3170             if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) {
   3171                 mTracker.sendChannelTuneTime(info.getCurrentChannel(),
   3172                         mTuneDurationTimer.reset());
   3173             }
   3174             // If updateChannelBanner() is called without delay, the stream info seems flickering
   3175             // when the channel is quickly changed.
   3176             if (!mHandler.hasMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE)
   3177                     && info.isVideoAvailable()) {
   3178                 if (System.currentTimeMillis() > mStreamInfoUpdateTimeThresholdMs) {
   3179                     updateChannelBannerAndShowIfNeeded(
   3180                             UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
   3181                 } else {
   3182                     mHandler.sendMessageDelayed(mHandler.obtainMessage(
   3183                             MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE),
   3184                             mStreamInfoUpdateTimeThresholdMs - System.currentTimeMillis());
   3185                 }
   3186             }
   3187 
   3188             applyDisplayRefreshRate(info.getVideoFrameRate());
   3189             mTvViewUiManager.updateTvView();
   3190             applyMultiAudio();
   3191             applyClosedCaption();
   3192             // TODO: Send command to TIS with checking the settings in TV and CaptionManager.
   3193             mOverlayManager.getMenu().onStreamInfoChanged();
   3194             if (mTvView.isVideoAvailable()) {
   3195                 mTvViewUiManager.fadeInTvView();
   3196             }
   3197             mHandler.removeCallbacks(mRestoreMainViewRunnable);
   3198             restoreMainTvView();
   3199         }
   3200 
   3201         @Override
   3202         public void onChannelRetuned(Uri channel) {
   3203             if (channel == null) {
   3204                 return;
   3205             }
   3206             Channel currentChannel =
   3207                     mChannelDataManager.getChannel(ContentUris.parseId(channel));
   3208             if (currentChannel == null) {
   3209                 Log.e(TAG, "onChannelRetuned is called but can't find a channel with the URI "
   3210                         + channel);
   3211                 return;
   3212             }
   3213             if (isChannelChangeKeyDownReceived()) {
   3214                 // Ignore this message if the user is changing the channel.
   3215                 return;
   3216             }
   3217             mChannelTuner.setCurrentChannel(currentChannel);
   3218             mTvView.setCurrentChannel(currentChannel);
   3219             updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE);
   3220         }
   3221 
   3222         @Override
   3223         public void onContentBlocked() {
   3224             mTuneDurationTimer.reset();
   3225             TvContentRating rating = mTvView.getBlockedContentRating();
   3226             // When tuneTo was called while TV view was shrunken, if the channel id is the same
   3227             // with the channel watched before shrunken, we allow the rating which was allowed
   3228             // before.
   3229             if (mWasUnderShrunkenTvView && mUnlockAllowedRatingBeforeShrunken
   3230                     && mChannelBeforeShrunkenTvView.equals(mChannel)
   3231                     && rating.equals(mAllowedRatingBeforeShrunken)) {
   3232                 mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView();
   3233                 mTvView.unblockContent(rating);
   3234             }
   3235 
   3236             updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
   3237             mTvViewUiManager.fadeInTvView();
   3238         }
   3239 
   3240         @Override
   3241         public void onContentAllowed() {
   3242             if (!isUnderShrunkenTvView()) {
   3243                 mUnlockAllowedRatingBeforeShrunken = false;
   3244             }
   3245             updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
   3246         }
   3247     }
   3248 }
   3249