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.content.res.Configuration;
     29 import android.database.Cursor;
     30 import android.hardware.display.DisplayManager;
     31 import android.media.tv.TvContentRating;
     32 import android.media.tv.TvContract;
     33 import android.media.tv.TvContract.Channels;
     34 import android.media.tv.TvInputInfo;
     35 import android.media.tv.TvInputManager;
     36 import android.media.tv.TvInputManager.TvInputCallback;
     37 import android.media.tv.TvTrackInfo;
     38 import android.media.tv.TvView.OnUnhandledInputEventListener;
     39 import android.net.Uri;
     40 import android.os.Build;
     41 import android.os.Bundle;
     42 import android.os.Handler;
     43 import android.os.Message;
     44 import android.os.PowerManager;
     45 import android.provider.Settings;
     46 import android.support.annotation.IntDef;
     47 import android.support.annotation.NonNull;
     48 import android.support.annotation.Nullable;
     49 import android.text.TextUtils;
     50 import android.util.ArraySet;
     51 import android.util.Log;
     52 import android.view.Display;
     53 import android.view.Gravity;
     54 import android.view.InputEvent;
     55 import android.view.KeyEvent;
     56 import android.view.View;
     57 import android.view.ViewGroup;
     58 import android.view.ViewTreeObserver;
     59 import android.view.Window;
     60 import android.view.WindowManager;
     61 import android.view.accessibility.AccessibilityEvent;
     62 import android.view.accessibility.AccessibilityManager;
     63 import android.widget.FrameLayout;
     64 import android.widget.Toast;
     65 
     66 import com.android.tv.analytics.SendChannelStatusRunnable;
     67 import com.android.tv.analytics.SendConfigInfoRunnable;
     68 import com.android.tv.analytics.Tracker;
     69 import com.android.tv.common.BuildConfig;
     70 import com.android.tv.common.MemoryManageable;
     71 import com.android.tv.common.SoftPreconditions;
     72 import com.android.tv.common.TvCommonUtils;
     73 import com.android.tv.common.TvContentRatingCache;
     74 import com.android.tv.common.WeakHandler;
     75 import com.android.tv.common.feature.CommonFeatures;
     76 import com.android.tv.common.ui.setup.OnActionClickListener;
     77 import com.android.tv.data.Channel;
     78 import com.android.tv.data.ChannelDataManager;
     79 import com.android.tv.data.OnCurrentProgramUpdatedListener;
     80 import com.android.tv.data.Program;
     81 import com.android.tv.data.ProgramDataManager;
     82 import com.android.tv.data.StreamInfo;
     83 import com.android.tv.data.WatchedHistoryManager;
     84 import com.android.tv.data.epg.EpgFetcher;
     85 import com.android.tv.dialog.HalfSizedDialogFragment;
     86 import com.android.tv.dialog.PinDialogFragment;
     87 import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
     88 import com.android.tv.dialog.SafeDismissDialogFragment;
     89 import com.android.tv.dvr.DvrManager;
     90 import com.android.tv.dvr.data.ScheduledRecording;
     91 import com.android.tv.dvr.recorder.ConflictChecker;
     92 import com.android.tv.dvr.ui.DvrStopRecordingFragment;
     93 import com.android.tv.dvr.ui.DvrUiHelper;
     94 import com.android.tv.menu.Menu;
     95 import com.android.tv.onboarding.OnboardingActivity;
     96 import com.android.tv.parental.ContentRatingsManager;
     97 import com.android.tv.parental.ParentalControlSettings;
     98 import com.android.tv.perf.EventNames;
     99 import com.android.tv.perf.PerformanceMonitor;
    100 import com.android.tv.perf.StubPerformanceMonitor;
    101 import com.android.tv.perf.TimerEvent;
    102 import com.android.tv.recommendation.ChannelPreviewUpdater;
    103 import com.android.tv.recommendation.NotificationService;
    104 import com.android.tv.search.ProgramGuideSearchFragment;
    105 import com.android.tv.tuner.TunerInputController;
    106 import com.android.tv.tuner.TunerPreferences;
    107 import com.android.tv.tuner.setup.TunerSetupActivity;
    108 import com.android.tv.tuner.tvinput.TunerTvInputService;
    109 import com.android.tv.ui.ChannelBannerView;
    110 import com.android.tv.ui.InputBannerView;
    111 import com.android.tv.ui.KeypadChannelSwitchView;
    112 import com.android.tv.ui.SelectInputView;
    113 import com.android.tv.ui.SelectInputView.OnInputSelectedCallback;
    114 import com.android.tv.ui.TunableTvView;
    115 import com.android.tv.ui.TunableTvView.BlockScreenType;
    116 import com.android.tv.ui.TunableTvView.OnTuneListener;
    117 import com.android.tv.ui.TvOverlayManager;
    118 import com.android.tv.ui.TvViewUiManager;
    119 import com.android.tv.ui.sidepanel.ClosedCaptionFragment;
    120 import com.android.tv.ui.sidepanel.CustomizeChannelListFragment;
    121 import com.android.tv.ui.sidepanel.DeveloperOptionFragment;
    122 import com.android.tv.ui.sidepanel.DisplayModeFragment;
    123 import com.android.tv.ui.sidepanel.MultiAudioFragment;
    124 import com.android.tv.ui.sidepanel.SettingsFragment;
    125 import com.android.tv.ui.sidepanel.SideFragment;
    126 import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment;
    127 import com.android.tv.util.AccountHelper;
    128 import com.android.tv.util.CaptionSettings;
    129 import com.android.tv.util.Debug;
    130 import com.android.tv.util.DurationTimer;
    131 import com.android.tv.util.ImageCache;
    132 import com.android.tv.util.OnboardingUtils;
    133 import com.android.tv.util.PermissionUtils;
    134 import com.android.tv.util.RecurringRunner;
    135 import com.android.tv.util.SetupUtils;
    136 import com.android.tv.util.SystemProperties;
    137 import com.android.tv.util.TvInputManagerHelper;
    138 import com.android.tv.util.TvSettings;
    139 import com.android.tv.util.TvTrackInfoUtils;
    140 import com.android.tv.util.Utils;
    141 import com.android.tv.util.ViewCache;
    142 
    143 import java.lang.annotation.Retention;
    144 import java.lang.annotation.RetentionPolicy;
    145 import java.util.ArrayDeque;
    146 import java.util.ArrayList;
    147 import java.util.HashSet;
    148 import java.util.List;
    149 import java.util.Objects;
    150 import java.util.Set;
    151 import java.util.concurrent.TimeUnit;
    152 
    153 /**
    154  * The main activity for the Live TV app.
    155  */
    156 public class MainActivity extends Activity implements OnActionClickListener, OnPinCheckedListener {
    157     private static final String TAG = "MainActivity";
    158     private static final boolean DEBUG = false;
    159 
    160     @Retention(RetentionPolicy.SOURCE)
    161     @IntDef({KEY_EVENT_HANDLER_RESULT_PASSTHROUGH, KEY_EVENT_HANDLER_RESULT_NOT_HANDLED,
    162         KEY_EVENT_HANDLER_RESULT_HANDLED, KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY})
    163     public @interface KeyHandlerResultType {}
    164     public static final int KEY_EVENT_HANDLER_RESULT_PASSTHROUGH = 0;
    165     public static final int KEY_EVENT_HANDLER_RESULT_NOT_HANDLED = 1;
    166     public static final int KEY_EVENT_HANDLER_RESULT_HANDLED = 2;
    167     public static final int KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY = 3;
    168 
    169     private static final boolean USE_BACK_KEY_LONG_PRESS = false;
    170 
    171     private static final float FRAME_RATE_FOR_FILM = 23.976f;
    172     private static final float FRAME_RATE_EPSILON = 0.1f;
    173 
    174 
    175     private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1;
    176     private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
    177 
    178     // Tracker screen names.
    179     public static final String SCREEN_NAME = "Main";
    180     private static final String SCREEN_BEHIND_NAME = "Behind";
    181 
    182     private static final float REFRESH_RATE_EPSILON = 0.01f;
    183     private static final HashSet<Integer> BLACKLIST_KEYCODE_TO_TIS;
    184     // These keys won't be passed to TIS in addition to gamepad buttons.
    185     static {
    186         BLACKLIST_KEYCODE_TO_TIS = new HashSet<>();
    187         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_TV_INPUT);
    188         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MENU);
    189         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_UP);
    190         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_DOWN);
    191         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_UP);
    192         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_DOWN);
    193         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_MUTE);
    194         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MUTE);
    195         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_SEARCH);
    196         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_WINDOW);
    197     }
    198 
    199 
    200     private static final IntentFilter SYSTEM_INTENT_FILTER = new IntentFilter();
    201     static {
    202         SYSTEM_INTENT_FILTER.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
    203         SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF);
    204         SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_ON);
    205         SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED);
    206     }
    207 
    208     private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
    209 
    210     private static final String KEY_INIT_CHANNEL_ID = "com.android.tv.init_channel_id";
    211 
    212     // Change channels with key long press.
    213     private static final int CHANNEL_CHANGE_NORMAL_SPEED_DURATION_MS = 3000;
    214     private static final int CHANNEL_CHANGE_DELAY_MS_IN_MAX_SPEED = 50;
    215     private static final int CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED = 200;
    216     private static final int CHANNEL_CHANGE_INITIAL_DELAY_MILLIS = 500;
    217 
    218     private static final int MSG_CHANNEL_DOWN_PRESSED = 1000;
    219     private static final int MSG_CHANNEL_UP_PRESSED = 1001;
    220 
    221     private static final int TVVIEW_SET_MAIN_TIMEOUT_MS = 3000;
    222 
    223     // Lazy initialization.
    224     // Delay 1 second in order not to interrupt the first tune.
    225     private static final long LAZY_INITIALIZATION_DELAY = TimeUnit.SECONDS.toMillis(1);
    226 
    227     private static final int UNDEFINED_TRACK_INDEX = -1;
    228     private static final long START_UP_TIMER_RESET_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(3);
    229 
    230     private AccessibilityManager mAccessibilityManager;
    231     private ChannelDataManager mChannelDataManager;
    232     private ProgramDataManager mProgramDataManager;
    233     private TvInputManagerHelper mTvInputManagerHelper;
    234     private ChannelTuner mChannelTuner;
    235     private final TvOptionsManager mTvOptionsManager = new TvOptionsManager(this);
    236     private TvViewUiManager mTvViewUiManager;
    237     private TimeShiftManager mTimeShiftManager;
    238     private Tracker mTracker;
    239     private final DurationTimer mMainDurationTimer = new DurationTimer();
    240     private final DurationTimer mTuneDurationTimer = new DurationTimer();
    241     private DvrManager mDvrManager;
    242     private ConflictChecker mDvrConflictChecker;
    243 
    244     private View mContentView;
    245     private TunableTvView mTvView;
    246     private Bundle mTuneParams;
    247     @Nullable
    248     private Uri mInitChannelUri;
    249     @Nullable
    250     private String mParentInputIdWhenScreenOff;
    251     private boolean mScreenOffIntentReceived;
    252     private boolean mShowProgramGuide;
    253     private boolean mShowSelectInputView;
    254     private TvInputInfo mInputToSetUp;
    255     private final List<MemoryManageable> mMemoryManageables = new ArrayList<>();
    256     private MediaSessionWrapper mMediaSessionWrapper;
    257     private final MyOnTuneListener mOnTuneListener = new MyOnTuneListener();
    258 
    259     private String mInputIdUnderSetup;
    260     private boolean mIsSetupActivityCalledByPopup;
    261     private AudioManagerHelper mAudioManagerHelper;
    262     private boolean mTunePending;
    263     private boolean mDebugNonFullSizeScreen;
    264     private boolean mActivityResumed;
    265     private boolean mActivityStarted;
    266     private boolean mShouldTuneToTunerChannel;
    267     private boolean mUseKeycodeBlacklist;
    268     private boolean mShowLockedChannelsTemporarily;
    269     private boolean mBackKeyPressed;
    270     private boolean mNeedShowBackKeyGuide;
    271     private boolean mVisibleBehind;
    272     private boolean mShowNewSourcesFragment = true;
    273     private String mTunerInputId;
    274     private boolean mOtherActivityLaunched;
    275     private PerformanceMonitor mPerformanceMonitor;
    276 
    277     private boolean mIsFilmModeSet;
    278     private float mDefaultRefreshRate;
    279 
    280     private TvOverlayManager mOverlayManager;
    281 
    282     // mIsCurrentChannelUnblockedByUser and mWasChannelUnblockedBeforeShrunkenByUser are used for
    283     // keeping the channel unblocking status while TV view is shrunken.
    284     private boolean mIsCurrentChannelUnblockedByUser;
    285     private boolean mWasChannelUnblockedBeforeShrunkenByUser;
    286     private Channel mChannelBeforeShrunkenTvView;
    287     private boolean mIsCompletingShrunkenTvView;
    288 
    289     private TvContentRating mLastAllowedRatingForCurrentChannel;
    290     private TvContentRating mAllowedRatingBeforeShrunken;
    291 
    292     private CaptionSettings mCaptionSettings;
    293     // Lazy initialization
    294     private boolean mLazyInitialized;
    295 
    296     private static final int MAX_RECENT_CHANNELS = 5;
    297     private final ArrayDeque<Long> mRecentChannels = new ArrayDeque<>(MAX_RECENT_CHANNELS);
    298 
    299     private RecurringRunner mSendConfigInfoRecurringRunner;
    300     private RecurringRunner mChannelStatusRecurringRunner;
    301 
    302     private final Handler mHandler = new MainActivityHandler(this);
    303     private final Set<OnActionClickListener> mOnActionClickListeners = new ArraySet<>();
    304 
    305     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    306         @Override
    307         public void onReceive(Context context, Intent intent) {
    308             switch (intent.getAction()) {
    309                 case Intent.ACTION_SCREEN_OFF:
    310                     if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF");
    311                     // We need to stop TvView, when the screen is turned off. If not and TIS uses
    312                     // MediaPlayer, a device may not go to the sleep mode and audio can be heard,
    313                     // because MediaPlayer keeps playing media by its wake lock.
    314                     mScreenOffIntentReceived = true;
    315                     markCurrentChannelDuringScreenOff();
    316                     stopAll(true);
    317                     break;
    318                 case Intent.ACTION_SCREEN_ON:
    319                     if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON");
    320                     if (!mActivityResumed && mVisibleBehind) {
    321                         // ACTION_SCREEN_ON is usually called after onResume. But, if media is
    322                         // played under launcher with requestVisibleBehind(true), onResume will
    323                         // not be called. In this case, we need to resume TvView explicitly.
    324                         resumeTvIfNeeded();
    325                     }
    326                     break;
    327                 case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED:
    328                     if (DEBUG) Log.d(TAG, "Received parental control settings change");
    329                     applyParentalControlSettings();
    330                     checkChannelLockNeeded(mTvView, null);
    331                     break;
    332                 case Intent.ACTION_TIME_CHANGED:
    333                     // Re-tune the current channel to prevent incorrect behavior of trick-play.
    334                     // See: b/37393628
    335                     if (mChannelTuner.getCurrentChannel() != null) {
    336                         tune(true);
    337                     }
    338                     break;
    339             }
    340         }
    341     };
    342 
    343     private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener =
    344             new OnCurrentProgramUpdatedListener() {
    345         @Override
    346         public void onCurrentProgramUpdated(long channelId, Program program) {
    347             // Do not update channel banner by this notification
    348             // when the time shifting is available.
    349             if (mTimeShiftManager.isAvailable()) {
    350                 return;
    351             }
    352             Channel channel = mTvView.getCurrentChannel();
    353             if (channel != null && channel.getId() == channelId) {
    354                 mOverlayManager.updateChannelBannerAndShowIfNeeded(
    355                         TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
    356                 mMediaSessionWrapper.update(mTvView.isBlocked(), channel, program);
    357             }
    358         }
    359     };
    360 
    361     private final ChannelTuner.Listener mChannelTunerListener =
    362             new ChannelTuner.Listener() {
    363                 @Override
    364                 public void onLoadFinished() {
    365                     Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
    366                             "MainActivity.mChannelTunerListener.onLoadFinished");
    367                     SetupUtils.getInstance(MainActivity.this).markNewChannelsBrowsable();
    368                     if (mActivityResumed) {
    369                         resumeTvIfNeeded();
    370                     }
    371                     mOverlayManager.onBrowsableChannelsUpdated();
    372                 }
    373 
    374                 @Override
    375                 public void onBrowsableChannelListChanged() {
    376                     mOverlayManager.onBrowsableChannelsUpdated();
    377                 }
    378 
    379                 @Override
    380                 public void onCurrentChannelUnavailable(Channel channel) {
    381                     if (mChannelTuner.moveToAdjacentBrowsableChannel(true)) {
    382                         tune(true);
    383                     } else {
    384                         stopTv("onCurrentChannelUnavailable()", false);
    385                     }
    386                 }
    387 
    388                 @Override
    389                 public void onChannelChanged(Channel previousChannel, Channel currentChannel) {}
    390             };
    391 
    392     private final Runnable mRestoreMainViewRunnable = new Runnable() {
    393         @Override
    394         public void run() {
    395             restoreMainTvView();
    396         }
    397     };
    398     private ProgramGuideSearchFragment mSearchFragment;
    399 
    400     private final TvInputCallback mTvInputCallback = new TvInputCallback() {
    401         @Override
    402         public void onInputAdded(String inputId) {
    403             if (Features.TUNER.isEnabled(MainActivity.this) && mTunerInputId.equals(inputId)
    404                     && TunerPreferences.shouldShowSetupActivity(MainActivity.this)) {
    405                 Intent intent = TunerSetupActivity.createSetupActivity(MainActivity.this);
    406                 startActivity(intent);
    407                 TunerPreferences.setShouldShowSetupActivity(MainActivity.this, false);
    408                 SetupUtils.getInstance(MainActivity.this).markAsKnownInput(mTunerInputId);
    409             }
    410         }
    411     };
    412 
    413     private void applyParentalControlSettings() {
    414         boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings()
    415                 .isParentalControlsEnabled();
    416         mTvView.onParentalControlChanged(parentalControlEnabled);
    417         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    418             ChannelPreviewUpdater.getInstance(this).updatePreviewDataForChannelsImmediately();
    419         }
    420     }
    421 
    422     @Override
    423     protected void onCreate(Bundle savedInstanceState) {
    424         TimerEvent timer = StubPerformanceMonitor.startBootstrapTimer();
    425         DurationTimer startUpDebugTimer = Debug.getTimer(Debug.TAG_START_UP_TIMER);
    426         if (!startUpDebugTimer.isStarted()
    427                 || startUpDebugTimer.getDuration() > START_UP_TIMER_RESET_THRESHOLD_MS) {
    428             // TvApplication can start by other reason before MainActivty is launched.
    429             // In this case, we restart the timer.
    430             startUpDebugTimer.start();
    431         }
    432         startUpDebugTimer.log("MainActivity.onCreate");
    433         if (DEBUG) Log.d(TAG,"onCreate()");
    434         TvApplication.setCurrentRunningProcess(this, true);
    435         super.onCreate(savedInstanceState);
    436         ApplicationSingletons applicationSingletons = TvApplication.getSingletons(this);
    437         if (!applicationSingletons.getTvInputManagerHelper().hasTvInputManager()) {
    438             Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
    439             finishAndRemoveTask();
    440             return;
    441         }
    442         mPerformanceMonitor = applicationSingletons.getPerformanceMonitor();
    443 
    444         TvApplication tvApplication = (TvApplication) getApplication();
    445         mChannelDataManager = tvApplication.getChannelDataManager();
    446         // In API 23, TvContract.isChannelUriForPassthroughInput is hidden.
    447         boolean isPassthroughInput =
    448                 TvContract.isChannelUriForPassthroughInput(getIntent().getData());
    449         boolean tuneToPassthroughInput = Intent.ACTION_VIEW.equals(getIntent().getAction())
    450                 && isPassthroughInput;
    451         boolean channelLoadedAndNoChannelAvailable = mChannelDataManager.isDbLoadFinished()
    452                 && mChannelDataManager.getChannelCount() <= 0;
    453         if ((OnboardingUtils.isFirstRunWithCurrentVersion(this)
    454                 || channelLoadedAndNoChannelAvailable)
    455                 && !tuneToPassthroughInput
    456                 && !TvCommonUtils.isRunningInTest()) {
    457             startOnboardingActivity();
    458             return;
    459         }
    460         setContentView(R.layout.activity_tv);
    461         mProgramDataManager = tvApplication.getProgramDataManager();
    462         mTvInputManagerHelper = tvApplication.getTvInputManagerHelper();
    463         mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view);
    464         mTvView.initialize(mProgramDataManager, mTvInputManagerHelper);
    465         mTvView.setOnUnhandledInputEventListener(new OnUnhandledInputEventListener() {
    466             @Override
    467             public boolean onUnhandledInputEvent(InputEvent event) {
    468                 if (isKeyEventBlocked()) {
    469                     return true;
    470                 }
    471                 if (event instanceof KeyEvent) {
    472                     KeyEvent keyEvent = (KeyEvent) event;
    473                     if (keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.isLongPress()) {
    474                         if (onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) {
    475                             return true;
    476                         }
    477                     }
    478                     if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
    479                         return onKeyUp(keyEvent.getKeyCode(), keyEvent);
    480                     } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
    481                         return onKeyDown(keyEvent.getKeyCode(), keyEvent);
    482                     }
    483                 }
    484                 return false;
    485             }
    486         });
    487         long channelId = Utils.getLastWatchedChannelId(this);
    488         String inputId = Utils.getLastWatchedTunerInputId(this);
    489         if (!isPassthroughInput && inputId != null
    490                 && channelId != Channel.INVALID_ID) {
    491             mTvView.warmUpInput(inputId, TvContract.buildChannelUri(channelId));
    492         }
    493 
    494         tvApplication.getMainActivityWrapper().onMainActivityCreated(this);
    495         if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
    496             Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show();
    497         }
    498         mTracker = tvApplication.getTracker();
    499         if (Features.TUNER.isEnabled(this)) {
    500             mTvInputManagerHelper.addCallback(mTvInputCallback);
    501         }
    502         mTunerInputId = TunerTvInputService.getInputId(this);
    503         mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID,
    504                 mOnCurrentProgramUpdatedListener);
    505         mProgramDataManager.setPrefetchEnabled(true);
    506         mChannelTuner = new ChannelTuner(mChannelDataManager, mTvInputManagerHelper);
    507         mChannelTuner.addListener(mChannelTunerListener);
    508         mChannelTuner.start();
    509         mMemoryManageables.add(mProgramDataManager);
    510         mMemoryManageables.add(ImageCache.getInstance());
    511         mMemoryManageables.add(TvContentRatingCache.getInstance());
    512         if (CommonFeatures.DVR.isEnabled(this)) {
    513             mDvrManager = tvApplication.getDvrManager();
    514         }
    515         mTimeShiftManager = new TimeShiftManager(this, mTvView, mProgramDataManager, mTracker,
    516                 new OnCurrentProgramUpdatedListener() {
    517                     @Override
    518                     public void onCurrentProgramUpdated(long channelId, Program program) {
    519                         mMediaSessionWrapper.update(mTvView.isBlocked(), getCurrentChannel(),
    520                                 program);
    521                         switch (mTimeShiftManager.getLastActionId()) {
    522                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND:
    523                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD:
    524                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS:
    525                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT:
    526                                 mOverlayManager.updateChannelBannerAndShowIfNeeded(
    527                                         TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
    528                                 break;
    529                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE:
    530                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY:
    531                             default:
    532                                 mOverlayManager.updateChannelBannerAndShowIfNeeded(
    533                                         TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
    534                                 break;
    535                         }
    536                     }
    537                 });
    538 
    539         DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
    540         Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
    541         mDefaultRefreshRate = display.getRefreshRate();
    542 
    543         if (!PermissionUtils.hasAccessWatchedHistory(this)) {
    544             WatchedHistoryManager watchedHistoryManager = new WatchedHistoryManager(
    545                     getApplicationContext());
    546             watchedHistoryManager.start();
    547             mTvView.setWatchedHistoryManager(watchedHistoryManager);
    548         }
    549         mTvViewUiManager = new TvViewUiManager(this, mTvView,
    550                 (FrameLayout) findViewById(android.R.id.content), mTvOptionsManager);
    551 
    552         mContentView = findViewById(android.R.id.content);
    553         ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container);
    554         ChannelBannerView channelBannerView = (ChannelBannerView) getLayoutInflater().inflate(
    555                 R.layout.channel_banner, sceneContainer, false);
    556         KeypadChannelSwitchView keypadChannelSwitchView = (KeypadChannelSwitchView)
    557                 getLayoutInflater().inflate(R.layout.keypad_channel_switch, sceneContainer, false);
    558         InputBannerView inputBannerView = (InputBannerView) getLayoutInflater()
    559                 .inflate(R.layout.input_banner, sceneContainer, false);
    560         SelectInputView selectInputView = (SelectInputView) getLayoutInflater()
    561                 .inflate(R.layout.select_input, sceneContainer, false);
    562         selectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() {
    563             @Override
    564             public void onTunerInputSelected() {
    565                 Channel currentChannel = mChannelTuner.getCurrentChannel();
    566                 if (currentChannel != null && !currentChannel.isPassthrough()) {
    567                     hideOverlays();
    568                 } else {
    569                     tuneToLastWatchedChannelForTunerInput();
    570                 }
    571             }
    572 
    573             @Override
    574             public void onPassthroughInputSelected(@NonNull TvInputInfo input) {
    575                 Channel currentChannel = mChannelTuner.getCurrentChannel();
    576                 String currentInputId = currentChannel == null ? null : currentChannel.getInputId();
    577                 if (TextUtils.equals(input.getId(), currentInputId)) {
    578                     hideOverlays();
    579                 } else {
    580                     tuneToChannel(Channel.createPassthroughChannel(input.getId()));
    581                 }
    582             }
    583 
    584             private void hideOverlays() {
    585                 getOverlayManager().hideOverlays(
    586                         TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
    587                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
    588                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
    589                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
    590                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
    591             }
    592         });
    593         mSearchFragment = new ProgramGuideSearchFragment();
    594         mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTvView, mTvOptionsManager,
    595                 keypadChannelSwitchView, channelBannerView, inputBannerView,
    596                 selectInputView, sceneContainer, mSearchFragment);
    597 
    598         mAudioManagerHelper = new AudioManagerHelper(this, mTvView);
    599         mMediaSessionWrapper = new MediaSessionWrapper(this);
    600 
    601         mTvViewUiManager.restoreDisplayMode(false);
    602         if (!handleIntent(getIntent())) {
    603             finish();
    604             return;
    605         }
    606 
    607         mAccessibilityManager =
    608                 (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
    609         mSendConfigInfoRecurringRunner = new RecurringRunner(this, TimeUnit.DAYS.toMillis(1),
    610                 new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper), null);
    611         mSendConfigInfoRecurringRunner.start();
    612         mChannelStatusRecurringRunner = SendChannelStatusRunnable
    613                 .startChannelStatusRecurringRunner(this, mTracker, mChannelDataManager);
    614 
    615         // To avoid not updating Rating systems when changing language.
    616         mTvInputManagerHelper.getContentRatingsManager().update();
    617         if (CommonFeatures.DVR.isEnabled(this)
    618                 && Features.SHOW_UPCOMING_CONFLICT_DIALOG.isEnabled(this)) {
    619             mDvrConflictChecker = new ConflictChecker(this);
    620         }
    621         initForTest();
    622         Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onCreate end");
    623         mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONCREATE);
    624     }
    625 
    626     private void startOnboardingActivity() {
    627         startActivity(OnboardingActivity.buildIntent(this, getIntent()));
    628         finish();
    629     }
    630 
    631     @Override
    632     public void onConfigurationChanged(Configuration newConfig) {
    633         super.onConfigurationChanged(newConfig);
    634         float density = getResources().getDisplayMetrics().density;
    635         mTvViewUiManager.onConfigurationChanged((int) (newConfig.screenWidthDp * density),
    636                 (int) (newConfig.screenHeightDp * density));
    637     }
    638 
    639     @Override
    640     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
    641             @NonNull int[] grantResults) {
    642         if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) {
    643             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    644                 // Start reload of dependent data
    645                 mChannelDataManager.reload();
    646                 mProgramDataManager.reload();
    647 
    648                 // Restart live channels.
    649                 Intent intent = getIntent();
    650                 finish();
    651                 startActivity(intent);
    652             } else {
    653                 Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied,
    654                         Toast.LENGTH_LONG).show();
    655                 finish();
    656             }
    657         }
    658     }
    659 
    660     @BlockScreenType private int getDesiredBlockScreenType() {
    661         if (!mActivityResumed) {
    662             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
    663         }
    664         if (isUnderShrunkenTvView()) {
    665             return TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW;
    666         }
    667         if (mOverlayManager.needHideTextOnMainView()) {
    668             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
    669         }
    670         SafeDismissDialogFragment currentDialog = mOverlayManager.getCurrentDialog();
    671         if (currentDialog != null) {
    672             // If PIN dialog is shown for unblocking the channel lock or content ratings lock,
    673             // keeping the unlocking message is more natural instead of changing it.
    674             if (currentDialog instanceof PinDialogFragment) {
    675                 int type = ((PinDialogFragment) currentDialog).getType();
    676                 if (type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL
    677                         || type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) {
    678                     return TunableTvView.BLOCK_SCREEN_TYPE_NORMAL;
    679                 }
    680             }
    681             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
    682         }
    683         if (mOverlayManager.isSetupFragmentActive()
    684                 || mOverlayManager.isNewSourcesFragmentActive()) {
    685             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
    686         }
    687         return TunableTvView.BLOCK_SCREEN_TYPE_NORMAL;
    688     }
    689 
    690     @Override
    691     protected void onNewIntent(Intent intent) {
    692         if (DEBUG) Log.d(TAG,"onNewIntent(): " + intent);
    693         if (mOverlayManager == null) {
    694             // It's called before onCreate. The intent will be handled at onCreate. b/30725058
    695             return;
    696         }
    697         mOverlayManager.getSideFragmentManager().hideAll(false);
    698         if (!handleIntent(intent) && !mActivityStarted) {
    699             // If the activity is stopped and not destroyed, finish the activity.
    700             // Otherwise, just ignore the intent.
    701             finish();
    702         }
    703     }
    704 
    705     @Override
    706     protected void onStart() {
    707         TimerEvent timer = mPerformanceMonitor.startTimer();
    708         if (DEBUG) Log.d(TAG,"onStart()");
    709         super.onStart();
    710         mScreenOffIntentReceived = false;
    711         mActivityStarted = true;
    712         mTracker.sendMainStart();
    713         mMainDurationTimer.start();
    714 
    715         applyParentalControlSettings();
    716         registerReceiver(mBroadcastReceiver, SYSTEM_INTENT_FILTER);
    717 
    718         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
    719             Intent notificationIntent = new Intent(this, NotificationService.class);
    720             notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION);
    721             startService(notificationIntent);
    722         }
    723         TunerInputController.executeNetworkTunerDiscoveryAsyncTask(this);
    724 
    725         EpgFetcher.getInstance(this).fetchImmediatelyIfNeeded();
    726         mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONSTART);
    727     }
    728 
    729     @Override
    730     protected void onResume() {
    731         TimerEvent timer = mPerformanceMonitor.startTimer();
    732         Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume start");
    733         if (DEBUG) Log.d(TAG, "onResume()");
    734         super.onResume();
    735         if (!PermissionUtils.hasAccessAllEpg(this)
    736                 && checkSelfPermission(PERMISSION_READ_TV_LISTINGS)
    737                     != PackageManager.PERMISSION_GRANTED) {
    738             requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS},
    739                     PERMISSIONS_REQUEST_READ_TV_LISTINGS);
    740         }
    741         mTracker.sendScreenView(SCREEN_NAME);
    742 
    743         SystemProperties.updateSystemProperties();
    744         mNeedShowBackKeyGuide = true;
    745         mActivityResumed = true;
    746         mShowNewSourcesFragment = true;
    747         mOtherActivityLaunched = false;
    748         mAudioManagerHelper.requestAudioFocus();
    749 
    750         if (mTvView.isPlaying()) {
    751             // Every time onResume() is called the activity will be assumed to not have requested
    752             // visible behind.
    753             requestVisibleBehind(true);
    754         }
    755         Set<String> failedScheduledRecordingInfoSet =
    756                 Utils.getFailedScheduledRecordingInfoSet(getApplicationContext());
    757         if (Utils.hasRecordingFailedReason(
    758                 getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)
    759                 && !failedScheduledRecordingInfoSet.isEmpty()) {
    760             runAfterAttachedToWindow(new Runnable() {
    761                 @Override
    762                 public void run() {
    763                     DvrUiHelper.showDvrInsufficientSpaceErrorDialog(MainActivity.this,
    764                             failedScheduledRecordingInfoSet);
    765                 }
    766             });
    767         }
    768 
    769         if (mChannelTuner.areAllChannelsLoaded()) {
    770             SetupUtils.getInstance(this).markNewChannelsBrowsable();
    771             resumeTvIfNeeded();
    772         }
    773         mOverlayManager.showMenuWithTimeShiftPauseIfNeeded();
    774 
    775         // NOTE: The following codes are related to pop up an overlay UI after resume. When
    776         // the following code is changed, please modify willShowOverlayUiWhenResume() accordingly.
    777         if (mInputToSetUp != null) {
    778             startSetupActivity(mInputToSetUp, false);
    779             mInputToSetUp = null;
    780         } else if (mShowProgramGuide) {
    781             mShowProgramGuide = false;
    782             mHandler.post(new Runnable() {
    783                 // This will delay the start of the animation until after the Live Channel app is
    784                 // shown. Without this the animation is completed before it is actually visible on
    785                 // the screen.
    786                 @Override
    787                 public void run() {
    788                     mOverlayManager.showProgramGuide();
    789                 }
    790             });
    791         } else if (mShowSelectInputView) {
    792             mShowSelectInputView = false;
    793             mHandler.post(new Runnable() {
    794                 // mShowSelectInputView is true when the activity is started/resumed because the
    795                 // TV_INPUT button was pressed in a different app.
    796                 // This will delay the start of the animation until after the Live Channel app is
    797                 // shown. Without this the animation is completed before it is actually visible on
    798                 // the screen.
    799                 @Override
    800                 public void run() {
    801                     mOverlayManager.showSelectInputView();
    802                 }
    803             });
    804         }
    805         if (mDvrConflictChecker != null) {
    806             mDvrConflictChecker.start();
    807         }
    808         Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume end");
    809         mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONRESUME);
    810     }
    811 
    812     @Override
    813     protected void onPause() {
    814         if (DEBUG) Log.d(TAG, "onPause()");
    815         if (mDvrConflictChecker != null) {
    816             mDvrConflictChecker.stop();
    817         }
    818         finishChannelChangeIfNeeded();
    819         mActivityResumed = false;
    820         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_DEFAULT);
    821         mTvView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_NO_UI);
    822         mBackKeyPressed = false;
    823         mShowLockedChannelsTemporarily = false;
    824         mShouldTuneToTunerChannel = false;
    825         if (!mVisibleBehind) {
    826             mAudioManagerHelper.abandonAudioFocus();
    827             mMediaSessionWrapper.setPlaybackState(false);
    828             mTracker.sendScreenView("");
    829         } else {
    830             mTracker.sendScreenView(SCREEN_BEHIND_NAME);
    831         }
    832         super.onPause();
    833     }
    834 
    835     /**
    836      * Returns true if {@link #onResume} is called and {@link #onPause} is not called yet.
    837      */
    838     public boolean isActivityResumed() {
    839         return mActivityResumed;
    840     }
    841 
    842     /**
    843      * Returns true if {@link #onStart} is called and {@link #onStop} is not called yet.
    844      */
    845     public boolean isActivityStarted() {
    846         return mActivityStarted;
    847     }
    848 
    849     @Override
    850     public boolean requestVisibleBehind(boolean enable) {
    851         boolean state = super.requestVisibleBehind(enable);
    852         mVisibleBehind = state;
    853         return state;
    854     }
    855 
    856     @Override
    857     public void onPinChecked(boolean checked, int type, String rating) {
    858         if (checked) {
    859             switch (type) {
    860                 case PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL:
    861                     blockOrUnblockScreen(mTvView, false);
    862                     mIsCurrentChannelUnblockedByUser = true;
    863                     break;
    864                 case PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM:
    865                     TvContentRating unblockedRating = TvContentRating.unflattenFromString(rating);
    866                     mLastAllowedRatingForCurrentChannel = unblockedRating;
    867                     mTvView.unblockContent(unblockedRating);
    868                     break;
    869                 case PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN:
    870                     mOverlayManager.getSideFragmentManager()
    871                             .show(new ParentalControlsFragment(), false);
    872                     // Pass through.
    873                 case PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN:
    874                     mOverlayManager.getSideFragmentManager().showSidePanel(true);
    875                     break;
    876             }
    877         } else if (type == PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN) {
    878             mOverlayManager.getSideFragmentManager().hideAll(false);
    879         }
    880     }
    881 
    882     private void resumeTvIfNeeded() {
    883         if (DEBUG) Log.d(TAG, "resumeTvIfNeeded()");
    884         if (!mTvView.isPlaying() || mInitChannelUri != null
    885                 || (mShouldTuneToTunerChannel && mChannelTuner.isCurrentChannelPassthrough())) {
    886             if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
    887                 // The target input may not be ready yet, especially, just after screen on.
    888                 String inputId = mInitChannelUri.getPathSegments().get(1);
    889                 TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(inputId);
    890                 if (input == null) {
    891                     input = mTvInputManagerHelper.getTvInputInfo(mParentInputIdWhenScreenOff);
    892                     if (input == null) {
    893                         SoftPreconditions.checkState(false, TAG, "Input disappear.");
    894                         finish();
    895                     } else {
    896                         mInitChannelUri =
    897                                 TvContract.buildChannelUriForPassthroughInput(input.getId());
    898                     }
    899                 }
    900             }
    901             mParentInputIdWhenScreenOff = null;
    902             startTv(mInitChannelUri);
    903             mInitChannelUri = null;
    904         }
    905         // Make sure TV app has the main TV view to handle the case that TvView is used in other
    906         // application.
    907         restoreMainTvView();
    908         mTvView.setBlockScreenType(getDesiredBlockScreenType());
    909     }
    910 
    911     private void startTv(Uri channelUri) {
    912         if (DEBUG) Log.d(TAG, "startTv Uri=" + channelUri);
    913         if ((channelUri == null || !TvContract.isChannelUriForPassthroughInput(channelUri))
    914                 && mChannelTuner.isCurrentChannelPassthrough()) {
    915             // For passthrough TV input, channelUri is always given. If TV app is launched
    916             // by TV app icon in a launcher, channelUri is null. So if passthrough TV input
    917             // is playing, we stop the passthrough TV input.
    918             stopTv();
    919         }
    920         SoftPreconditions.checkState(TvContract.isChannelUriForPassthroughInput(channelUri)
    921                 || mChannelTuner.areAllChannelsLoaded(),
    922                 TAG, "startTV assumes that ChannelDataManager is already loaded.");
    923         if (mTvView.isPlaying()) {
    924             // TV has already started.
    925             if (channelUri == null || channelUri.equals(mChannelTuner.getCurrentChannelUri())) {
    926                 // Simply adjust the volume without tune.
    927                 mAudioManagerHelper.setVolumeByAudioFocusStatus();
    928                 return;
    929             }
    930             stopTv();
    931         }
    932         if (mChannelTuner.getCurrentChannel() != null) {
    933             Log.w(TAG, "The current channel should be reset before");
    934             mChannelTuner.resetCurrentChannel();
    935         }
    936         if (channelUri == null) {
    937             // If any initial channel id is not given, remember the last channel the user watched.
    938             long channelId = Utils.getLastWatchedChannelId(this);
    939             if (channelId != Channel.INVALID_ID) {
    940                 channelUri = TvContract.buildChannelUri(channelId);
    941             }
    942         }
    943 
    944         if (channelUri == null) {
    945             mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
    946         } else {
    947             if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
    948                 Channel channel = Channel.createPassthroughChannel(channelUri);
    949                 mChannelTuner.moveToChannel(channel);
    950             } else {
    951                 long channelId = ContentUris.parseId(channelUri);
    952                 Channel channel = mChannelDataManager.getChannel(channelId);
    953                 if (channel == null || !mChannelTuner.moveToChannel(channel)) {
    954                     mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
    955                     Log.w(TAG, "The requested channel (id=" + channelId + ") doesn't exist. "
    956                             + "The first channel will be tuned to.");
    957                 }
    958             }
    959         }
    960 
    961         mTvView.start();
    962         mAudioManagerHelper.setVolumeByAudioFocusStatus();
    963         tune(true);
    964     }
    965 
    966     @Override
    967     protected void onStop() {
    968         if (DEBUG) Log.d(TAG, "onStop()");
    969         if (mScreenOffIntentReceived) {
    970             mScreenOffIntentReceived = false;
    971         } else {
    972             PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
    973             if (!powerManager.isInteractive()) {
    974                 // We added to check isInteractive as well as SCREEN_OFF intent, because
    975                 // calling timing of the intent SCREEN_OFF is not consistent. b/25953633.
    976                 // If we verify that checking isInteractive is enough, we can remove the logic
    977                 // for SCREEN_OFF intent.
    978                 markCurrentChannelDuringScreenOff();
    979             }
    980         }
    981         mActivityStarted = false;
    982         stopAll(false);
    983         unregisterReceiver(mBroadcastReceiver);
    984         mTracker.sendMainStop(mMainDurationTimer.reset());
    985         super.onStop();
    986     }
    987 
    988     /**
    989      * Handles screen off to keep the current channel for next screen on.
    990      */
    991     private void markCurrentChannelDuringScreenOff() {
    992         mInitChannelUri = mChannelTuner.getCurrentChannelUri();
    993         if (mChannelTuner.isCurrentChannelPassthrough()) {
    994             // When ACTION_SCREEN_OFF is invoked, some CEC devices may be already
    995             // removed. So we need to get the input info from ChannelTuner instead of
    996             // TvInputManagerHelper.
    997             TvInputInfo input = mChannelTuner.getCurrentInputInfo();
    998             mParentInputIdWhenScreenOff = input.getParentId();
    999             if (DEBUG) Log.d(TAG, "Parent input: " + mParentInputIdWhenScreenOff);
   1000         }
   1001     }
   1002 
   1003     private void stopAll(boolean keepVisibleBehind) {
   1004         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
   1005         stopTv("stopAll()", keepVisibleBehind);
   1006     }
   1007 
   1008     public TvInputManagerHelper getTvInputManagerHelper() {
   1009         return mTvInputManagerHelper;
   1010     }
   1011 
   1012     /**
   1013      * Starts setup activity for the given input {@code input}.
   1014      *
   1015      * @param calledByPopup If true, startSetupActivity is invoked from the setup fragment.
   1016      */
   1017     public void startSetupActivity(TvInputInfo input, boolean calledByPopup) {
   1018         Intent intent = TvCommonUtils.createSetupIntent(input);
   1019         if (intent == null) {
   1020             Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT).show();
   1021             return;
   1022         }
   1023         // Even though other app can handle the intent, the setup launched by Live channels
   1024         // should go through Live channels SetupPassthroughActivity.
   1025         intent.setComponent(new ComponentName(this, SetupPassthroughActivity.class));
   1026         try {
   1027             // Now we know that the user intends to set up this input. Grant permission for writing
   1028             // EPG data.
   1029             SetupUtils.grantEpgPermission(this, input.getServiceInfo().packageName);
   1030 
   1031             mInputIdUnderSetup = input.getId();
   1032             mIsSetupActivityCalledByPopup = calledByPopup;
   1033             // Call requestVisibleBehind(false) before starting other activity.
   1034             // In Activity.requestVisibleBehind(false), this activity is scheduled to be stopped
   1035             // immediately if other activity is about to start. And this activity is scheduled to
   1036             // to be stopped again after onPause().
   1037             stopTv("startSetupActivity()", false);
   1038             startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
   1039         } catch (ActivityNotFoundException e) {
   1040             mInputIdUnderSetup = null;
   1041             Toast.makeText(this, getString(R.string.msg_unable_to_start_setup_activity,
   1042                     input.loadLabel(this)), Toast.LENGTH_SHORT).show();
   1043             return;
   1044         }
   1045         if (calledByPopup) {
   1046             mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
   1047                     | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
   1048         } else {
   1049             mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
   1050                     | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY);
   1051         }
   1052     }
   1053 
   1054     public boolean hasCaptioningSettingsActivity() {
   1055         return Utils.isIntentAvailable(this, new Intent(Settings.ACTION_CAPTIONING_SETTINGS));
   1056     }
   1057 
   1058     public void startSystemCaptioningSettingsActivity() {
   1059         Intent intent = new Intent(Settings.ACTION_CAPTIONING_SETTINGS);
   1060         try {
   1061             startActivitySafe(intent);
   1062         } catch (ActivityNotFoundException e) {
   1063             Toast.makeText(this, getString(R.string.msg_unable_to_start_system_captioning_settings),
   1064                     Toast.LENGTH_SHORT).show();
   1065         }
   1066     }
   1067 
   1068     public ChannelDataManager getChannelDataManager() {
   1069         return mChannelDataManager;
   1070     }
   1071 
   1072     public ProgramDataManager getProgramDataManager() {
   1073         return mProgramDataManager;
   1074     }
   1075 
   1076     public TvOptionsManager getTvOptionsManager() {
   1077         return mTvOptionsManager;
   1078     }
   1079 
   1080     public TvViewUiManager getTvViewUiManager() {
   1081         return mTvViewUiManager;
   1082     }
   1083 
   1084     public TimeShiftManager getTimeShiftManager() {
   1085         return mTimeShiftManager;
   1086     }
   1087 
   1088     /**
   1089      * Returns the instance of {@link TvOverlayManager}.
   1090      */
   1091     public TvOverlayManager getOverlayManager() {
   1092         return mOverlayManager;
   1093     }
   1094 
   1095     /**
   1096      * Returns the {@link ConflictChecker}.
   1097      */
   1098     @Nullable
   1099     public ConflictChecker getDvrConflictChecker() {
   1100         return mDvrConflictChecker;
   1101     }
   1102 
   1103     public Channel getCurrentChannel() {
   1104         return mChannelTuner.getCurrentChannel();
   1105     }
   1106 
   1107     public long getCurrentChannelId() {
   1108         return mChannelTuner.getCurrentChannelId();
   1109     }
   1110 
   1111     /**
   1112      * Returns the current program which the user is watching right now.<p>
   1113      *
   1114      * It might be a live program. If the time shifting is available, it can be a past program, too.
   1115      */
   1116     public Program getCurrentProgram() {
   1117         if (!isChannelChangeKeyDownReceived() && mTimeShiftManager.isAvailable()) {
   1118             // We shouldn't get current program from TimeShiftManager during channel tunning
   1119             return mTimeShiftManager.getCurrentProgram();
   1120         }
   1121         return mProgramDataManager.getCurrentProgram(getCurrentChannelId());
   1122     }
   1123 
   1124     /**
   1125      * Returns the current playing time in milliseconds.<p>
   1126      *
   1127      * If the time shifting is available, the time is the playing position of the program,
   1128      * otherwise, the system current time.
   1129      */
   1130     public long getCurrentPlayingPosition() {
   1131         if (mTimeShiftManager.isAvailable()) {
   1132             return mTimeShiftManager.getCurrentPositionMs();
   1133         }
   1134         return System.currentTimeMillis();
   1135     }
   1136 
   1137     private Channel getBrowsableChannel() {
   1138         Channel curChannel = mChannelTuner.getCurrentChannel();
   1139         if (curChannel != null && curChannel.isBrowsable()) {
   1140             return curChannel;
   1141         } else {
   1142             return mChannelTuner.getAdjacentBrowsableChannel(true);
   1143         }
   1144     }
   1145 
   1146     /**
   1147      * Call {@link Activity#startActivity} in a safe way.
   1148      *
   1149      * @see LauncherActivity
   1150      */
   1151     public void startActivitySafe(Intent intent) {
   1152         LauncherActivity.startActivitySafe(this, intent);
   1153     }
   1154 
   1155     /**
   1156      * Call {@link Activity#startActivityForResult} in a safe way.
   1157      *
   1158      * @see LauncherActivity
   1159      */
   1160     private void startActivityForResultSafe(Intent intent, int requestCode) {
   1161         LauncherActivity.startActivityForResultSafe(this, intent, requestCode);
   1162     }
   1163 
   1164     /**
   1165      * Show settings fragment.
   1166      */
   1167     public void showSettingsFragment() {
   1168         if (!mChannelTuner.areAllChannelsLoaded()) {
   1169             // Show ChannelSourcesFragment only if all the channels are loaded.
   1170             return;
   1171         }
   1172         mOverlayManager.getSideFragmentManager().show(new SettingsFragment());
   1173     }
   1174 
   1175     public void showMerchantCollection() {
   1176         startActivitySafe(OnboardingUtils.ONLINE_STORE_INTENT);
   1177     }
   1178 
   1179     /**
   1180      * It is called when shrunken TvView is desired, such as EditChannelFragment and
   1181      * ChannelsLockedFragment.
   1182      */
   1183     public void startShrunkenTvView(boolean showLockedChannelsTemporarily,
   1184             boolean willMainViewBeTunerInput) {
   1185         mChannelBeforeShrunkenTvView = mTvView.getCurrentChannel();
   1186         mWasChannelUnblockedBeforeShrunkenByUser = mIsCurrentChannelUnblockedByUser;
   1187         mAllowedRatingBeforeShrunken = mLastAllowedRatingForCurrentChannel;
   1188         mTvViewUiManager.startShrunkenTvView();
   1189 
   1190         if (showLockedChannelsTemporarily) {
   1191             mShowLockedChannelsTemporarily = true;
   1192             checkChannelLockNeeded(mTvView, null);
   1193         }
   1194 
   1195         mTvView.setBlockScreenType(getDesiredBlockScreenType());
   1196     }
   1197 
   1198     /**
   1199      * It is called when shrunken TvView is no longer desired, such as EditChannelFragment and
   1200      * ChannelsLockedFragment.
   1201      */
   1202     public void endShrunkenTvView() {
   1203         mTvViewUiManager.endShrunkenTvView();
   1204         mIsCompletingShrunkenTvView = true;
   1205 
   1206         Channel returnChannel = mChannelBeforeShrunkenTvView;
   1207         if (returnChannel == null
   1208                 || (!returnChannel.isPassthrough() && !returnChannel.isBrowsable())) {
   1209             // Try to tune to the next best channel instead.
   1210             returnChannel = getBrowsableChannel();
   1211         }
   1212         mShowLockedChannelsTemporarily = false;
   1213 
   1214         // The current channel is mTvView.getCurrentChannel() and need to tune to the returnChannel.
   1215         if (!Objects.equals(mTvView.getCurrentChannel(), returnChannel)) {
   1216             final Channel channel = returnChannel;
   1217             Runnable tuneAction = new Runnable() {
   1218                 @Override
   1219                 public void run() {
   1220                     tuneToChannel(channel);
   1221                     if (mChannelBeforeShrunkenTvView == null
   1222                             || !mChannelBeforeShrunkenTvView.equals(channel)) {
   1223                         Utils.setLastWatchedChannel(MainActivity.this, channel);
   1224                     }
   1225                     mIsCompletingShrunkenTvView = false;
   1226                     mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
   1227                     mTvView.setBlockScreenType(getDesiredBlockScreenType());
   1228                 }
   1229             };
   1230             mTvViewUiManager.fadeOutTvView(tuneAction);
   1231             // Will automatically fade-in when video becomes available.
   1232         } else {
   1233             checkChannelLockNeeded(mTvView, null);
   1234             mIsCompletingShrunkenTvView = false;
   1235             mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
   1236             mTvView.setBlockScreenType(getDesiredBlockScreenType());
   1237         }
   1238     }
   1239 
   1240     private boolean isUnderShrunkenTvView() {
   1241         return mTvViewUiManager.isUnderShrunkenTvView() || mIsCompletingShrunkenTvView;
   1242     }
   1243 
   1244     /**
   1245      * Returns {@code true} if the tunable tv view is blocked by resource conflict or by parental
   1246      * control, otherwise {@code false}.
   1247      */
   1248     public boolean isScreenBlockedByResourceConflictOrParentalControl() {
   1249         return mTvView.getVideoUnavailableReason()
   1250                 == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE || mTvView.isBlocked();
   1251     }
   1252 
   1253     @Override
   1254     public void onActivityResult(int requestCode, int resultCode, Intent data) {
   1255         if (requestCode == REQUEST_CODE_START_SETUP_ACTIVITY) {
   1256             if (resultCode == RESULT_OK) {
   1257                 int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup);
   1258                 String text;
   1259                 if (count > 0) {
   1260                     text = getResources().getQuantityString(R.plurals.msg_channel_added,
   1261                             count, count);
   1262                 } else {
   1263                     text = getString(R.string.msg_no_channel_added);
   1264                 }
   1265                 Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
   1266                 mInputIdUnderSetup = null;
   1267                 if (mChannelTuner.getCurrentChannel() == null) {
   1268                     mChannelTuner.moveToAdjacentBrowsableChannel(true);
   1269                 }
   1270                 if (mTunePending) {
   1271                     tune(true);
   1272                 }
   1273             } else {
   1274                 mInputIdUnderSetup = null;
   1275             }
   1276             if (!mIsSetupActivityCalledByPopup) {
   1277                 mOverlayManager.getSideFragmentManager().showSidePanel(false);
   1278             }
   1279         }
   1280         if (data != null) {
   1281             String errorMessage = data.getStringExtra(LauncherActivity.ERROR_MESSAGE);
   1282             if (!TextUtils.isEmpty(errorMessage)) {
   1283                 Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
   1284             }
   1285         }
   1286     }
   1287 
   1288     @Override
   1289     public boolean dispatchKeyEvent(KeyEvent event) {
   1290         if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
   1291         // If an activity is closed on a back key down event, back key down events with none zero
   1292         // repeat count or a back key up event can be happened without the first back key down
   1293         // event which should be ignored in this activity.
   1294         if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
   1295             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
   1296                 mBackKeyPressed = true;
   1297             }
   1298             if (!mBackKeyPressed) {
   1299                 return true;
   1300             }
   1301             if (event.getAction() == KeyEvent.ACTION_UP) {
   1302                 mBackKeyPressed = false;
   1303             }
   1304         }
   1305 
   1306         // When side panel is closing, it has the focus.
   1307         // Keep the focus, but just don't deliver the key events.
   1308         if ((mContentView.hasFocusable() && !mOverlayManager.getSideFragmentManager().isHiding())
   1309                 || mOverlayManager.getSideFragmentManager().isActive()) {
   1310             return super.dispatchKeyEvent(event);
   1311         }
   1312         if (BLACKLIST_KEYCODE_TO_TIS.contains(event.getKeyCode())
   1313                 || KeyEvent.isGamepadButton(event.getKeyCode())) {
   1314             // If the event is in blacklisted or gamepad key, do not pass it to session.
   1315             // Gamepad keys are blacklisted to support TV UIs and here's the detail.
   1316             // If there's a TIS granted RECEIVE_INPUT_EVENT, TIF sends key events to TIS
   1317             // and return immediately saying that the event is handled.
   1318             // In this case, fallback key will be injected but with FLAG_CANCELED
   1319             // while gamepads support DPAD_CENTER and BACK by fallback.
   1320             // Since we don't expect that TIS want to handle gamepad buttons now,
   1321             // blacklist gamepad buttons and wait for next fallback keys.
   1322             // TODO: Need to consider other fallback keys (e.g. ESCAPE)
   1323             return super.dispatchKeyEvent(event);
   1324         }
   1325         return dispatchKeyEventToSession(event) || super.dispatchKeyEvent(event);
   1326     }
   1327 
   1328     /**
   1329      * Notifies the key input focus is changed to the TV view.
   1330      */
   1331     public void updateKeyInputFocus() {
   1332         mHandler.post(new Runnable() {
   1333             @Override
   1334             public void run() {
   1335                 mTvView.setBlockScreenType(getDesiredBlockScreenType());
   1336             }
   1337         });
   1338     }
   1339 
   1340     // It should be called before onResume.
   1341     private boolean handleIntent(Intent intent) {
   1342         // Reset the closed caption settings when the activity is 1)created or 2) restarted.
   1343         // And do not reset while TvView is playing.
   1344         if (!mTvView.isPlaying()) {
   1345             mCaptionSettings = new CaptionSettings(this);
   1346         }
   1347         mShouldTuneToTunerChannel = intent.getBooleanExtra(Utils.EXTRA_KEY_FROM_LAUNCHER, false);
   1348         mInitChannelUri = null;
   1349 
   1350         String extraAction = intent.getStringExtra(Utils.EXTRA_KEY_ACTION);
   1351         if (!TextUtils.isEmpty(extraAction)) {
   1352             if (DEBUG) Log.d(TAG, "Got an extra action: " + extraAction);
   1353             if (Utils.EXTRA_ACTION_SHOW_TV_INPUT.equals(extraAction)) {
   1354                 String lastWatchedChannelUri = Utils.getLastWatchedChannelUri(this);
   1355                 if (lastWatchedChannelUri != null) {
   1356                     mInitChannelUri = Uri.parse(lastWatchedChannelUri);
   1357                 }
   1358                 mShowSelectInputView = true;
   1359             }
   1360         }
   1361 
   1362         if (TvInputManager.ACTION_SETUP_INPUTS.equals(intent.getAction())) {
   1363             runAfterAttachedToWindow(new Runnable() {
   1364                 @Override
   1365                 public void run() {
   1366                     mOverlayManager.showSetupFragment();
   1367                 }
   1368             });
   1369         } else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
   1370             Uri uri = intent.getData();
   1371             if (Utils.isProgramsUri(uri)) {
   1372                 // When the URI points to the programs (directory, not an individual item), go to
   1373                 // the program guide. The intention here is to respond to
   1374                 // "content://android.media.tv/program", not
   1375                 // "content://android.media.tv/program/XXX".
   1376                 // Later, we might want to add handling of individual programs too.
   1377                 mShowProgramGuide = true;
   1378                 return true;
   1379             }
   1380             // In case the channel is given explicitly, use it.
   1381             mInitChannelUri = uri;
   1382             if (DEBUG) Log.d(TAG, "ACTION_VIEW with " + mInitChannelUri);
   1383             if (Channels.CONTENT_URI.equals(mInitChannelUri)) {
   1384                 // Tune to default channel.
   1385                 mInitChannelUri = null;
   1386                 mShouldTuneToTunerChannel = true;
   1387                 return true;
   1388             }
   1389             if ((!Utils.isChannelUriForOneChannel(mInitChannelUri)
   1390                     && !Utils.isChannelUriForInput(mInitChannelUri))) {
   1391                 Log.w(TAG, "Malformed channel uri " + mInitChannelUri
   1392                         + " tuning to default instead");
   1393                 mInitChannelUri = null;
   1394                 return true;
   1395             }
   1396             mTuneParams = intent.getExtras();
   1397             if (mTuneParams == null) {
   1398                 mTuneParams = new Bundle();
   1399             }
   1400             if (Utils.isChannelUriForTunerInput(mInitChannelUri)) {
   1401                 long channelId = ContentUris.parseId(mInitChannelUri);
   1402                 mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId);
   1403             } else if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
   1404                 // If mInitChannelUri is for a passthrough TV input.
   1405                 String inputId = mInitChannelUri.getPathSegments().get(1);
   1406                 TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(inputId);
   1407                 if (input == null) {
   1408                     mInitChannelUri = null;
   1409                     Toast.makeText(this, R.string.msg_no_specific_input, Toast.LENGTH_SHORT).show();
   1410                     return false;
   1411                 } else if (!input.isPassthroughInput()) {
   1412                     mInitChannelUri = null;
   1413                     Toast.makeText(this, R.string.msg_not_passthrough_input, Toast.LENGTH_SHORT)
   1414                             .show();
   1415                     return false;
   1416                 }
   1417             } else if (mInitChannelUri != null) {
   1418                 // Handle the URI built by TvContract.buildChannelsUriForInput().
   1419                 String inputId = mInitChannelUri.getQueryParameter("input");
   1420                 long channelId = Utils.getLastWatchedChannelIdForInput(this, inputId);
   1421                 if (channelId == Channel.INVALID_ID) {
   1422                     String[] projection = { Channels._ID };
   1423                     long time = System.currentTimeMillis();
   1424                     try (Cursor cursor = getContentResolver().query(uri, projection,
   1425                             null, null, null)) {
   1426                         if (cursor != null && cursor.moveToNext()) {
   1427                             channelId = cursor.getLong(0);
   1428                         }
   1429                     }
   1430                     Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity queries DB for "
   1431                             + "last channel check (" + (System.currentTimeMillis() - time) + "ms)");
   1432                 }
   1433                 if (channelId == Channel.INVALID_ID) {
   1434                     // Couldn't find any channel probably because the input hasn't been set up.
   1435                     // Try to set it up.
   1436                     mInitChannelUri = null;
   1437                     mInputToSetUp = mTvInputManagerHelper.getTvInputInfo(inputId);
   1438                 } else {
   1439                     mInitChannelUri = TvContract.buildChannelUri(channelId);
   1440                     mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId);
   1441                 }
   1442             }
   1443         }
   1444         return true;
   1445     }
   1446 
   1447     private void stopTv() {
   1448         stopTv(null, false);
   1449     }
   1450 
   1451     private void stopTv(String logForCaller, boolean keepVisibleBehind) {
   1452         if (logForCaller != null) {
   1453             Log.i(TAG, "stopTv is called at " + logForCaller + ".");
   1454         } else {
   1455             if (DEBUG) Log.d(TAG, "stopTv()");
   1456         }
   1457         if (mTvView.isPlaying()) {
   1458             mTvView.stop();
   1459             if (!keepVisibleBehind) {
   1460                 requestVisibleBehind(false);
   1461             }
   1462             mAudioManagerHelper.abandonAudioFocus();
   1463             mMediaSessionWrapper.setPlaybackState(false);
   1464         }
   1465         TvApplication.getSingletons(this).getMainActivityWrapper()
   1466                 .notifyCurrentChannelChange(this, null);
   1467         mChannelTuner.resetCurrentChannel();
   1468         mTunePending = false;
   1469     }
   1470 
   1471     private void scheduleRestoreMainTvView() {
   1472         mHandler.removeCallbacks(mRestoreMainViewRunnable);
   1473         mHandler.postDelayed(mRestoreMainViewRunnable, TVVIEW_SET_MAIN_TIMEOUT_MS);
   1474     }
   1475 
   1476     /**
   1477      * Says {@code text} when accessibility is turned on.
   1478      */
   1479     private void sendAccessibilityText(String text) {
   1480         if (mAccessibilityManager.isEnabled()) {
   1481             AccessibilityEvent event = AccessibilityEvent.obtain();
   1482             event.setClassName(getClass().getName());
   1483             event.setPackageName(getPackageName());
   1484             event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
   1485             event.getText().add(text);
   1486             mAccessibilityManager.sendAccessibilityEvent(event);
   1487         }
   1488     }
   1489 
   1490     private void tune(boolean updateChannelBanner) {
   1491         if (DEBUG) Log.d(TAG, "tune()");
   1492         mTuneDurationTimer.start();
   1493 
   1494         lazyInitializeIfNeeded();
   1495 
   1496         // Prerequisites to be able to tune.
   1497         if (mInputIdUnderSetup != null) {
   1498             mTunePending = true;
   1499             return;
   1500         }
   1501         mTunePending = false;
   1502         final Channel channel = mChannelTuner.getCurrentChannel();
   1503         SoftPreconditions.checkState(channel != null);
   1504         if (channel == null) {
   1505             return;
   1506         }
   1507         if (!mChannelTuner.isCurrentChannelPassthrough()) {
   1508             if (mTvInputManagerHelper.getTunerTvInputSize() == 0) {
   1509                 Toast.makeText(this, R.string.msg_no_input, Toast.LENGTH_SHORT).show();
   1510                 finish();
   1511                 return;
   1512             }
   1513             SetupUtils setupUtils = SetupUtils.getInstance(this);
   1514             if (setupUtils.isFirstTune()) {
   1515                 if (!mChannelTuner.areAllChannelsLoaded()) {
   1516                     // tune() will be called, once all channels are loaded.
   1517                     stopTv("tune()", false);
   1518                     return;
   1519                 }
   1520                 if (mChannelDataManager.getChannelCount() > 0) {
   1521                     mOverlayManager.showIntroDialog();
   1522                 } else {
   1523                     startOnboardingActivity();
   1524                     return;
   1525                 }
   1526             }
   1527             mShowNewSourcesFragment = false;
   1528             if (mChannelTuner.getBrowsableChannelCount() == 0
   1529                     && mChannelDataManager.getChannelCount() > 0
   1530                     && !mOverlayManager.getSideFragmentManager().isActive()) {
   1531                 if (!mChannelTuner.areAllChannelsLoaded()) {
   1532                     return;
   1533                 }
   1534                 if (mTvInputManagerHelper.getTunerTvInputSize() == 1) {
   1535                     mOverlayManager.getSideFragmentManager().show(
   1536                             new CustomizeChannelListFragment());
   1537                 } else {
   1538                     mOverlayManager.showSetupFragment();
   1539                 }
   1540                 return;
   1541             }
   1542             if (!TvCommonUtils.isRunningInTest() && mShowNewSourcesFragment
   1543                     && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) {
   1544                 // Show new channel sources fragment.
   1545                 runAfterAttachedToWindow(new Runnable() {
   1546                     @Override
   1547                     public void run() {
   1548                         mOverlayManager.runAfterOverlaysAreClosed(new Runnable() {
   1549                             @Override
   1550                             public void run() {
   1551                                 mOverlayManager.showNewSourcesFragment();
   1552                             }
   1553                         });
   1554                     }
   1555                 });
   1556             }
   1557             setupUtils.onTuned();
   1558             if (mTuneParams != null) {
   1559                 Long initChannelId = mTuneParams.getLong(KEY_INIT_CHANNEL_ID);
   1560                 if (initChannelId == channel.getId()) {
   1561                     mTuneParams.remove(KEY_INIT_CHANNEL_ID);
   1562                 } else {
   1563                     mTuneParams = null;
   1564                 }
   1565             }
   1566         }
   1567 
   1568         mIsCurrentChannelUnblockedByUser = false;
   1569         if (!isUnderShrunkenTvView()) {
   1570             mLastAllowedRatingForCurrentChannel = null;
   1571         }
   1572         // For every tune, we need to inform the tuned channel or input to a user,
   1573         // if Talkback is turned on.
   1574         sendAccessibilityText(!mChannelTuner.isCurrentChannelPassthrough() ?
   1575                 Utils.loadLabel(this, mTvInputManagerHelper.getTvInputInfo(channel.getInputId()))
   1576                 : channel.getDisplayText());
   1577 
   1578         boolean success = mTvView.tuneTo(channel, mTuneParams, mOnTuneListener);
   1579         mOnTuneListener.onTune(channel, isUnderShrunkenTvView());
   1580 
   1581         mTuneParams = null;
   1582         if (!success) {
   1583             Toast.makeText(this, R.string.msg_tune_failed, Toast.LENGTH_SHORT).show();
   1584             return;
   1585         }
   1586 
   1587         // Explicitly make the TV view main to make the selected input an HDMI-CEC active source.
   1588         mTvView.setMain();
   1589         scheduleRestoreMainTvView();
   1590         if (!isUnderShrunkenTvView()) {
   1591             if (!channel.isPassthrough()) {
   1592                 addToRecentChannels(channel.getId());
   1593             }
   1594             Utils.setLastWatchedChannel(this, channel);
   1595             TvApplication.getSingletons(this).getMainActivityWrapper()
   1596                     .notifyCurrentChannelChange(this, channel);
   1597         }
   1598         // We have to provide channel here instead of using TvView's channel, because TvView's
   1599         // channel might be null when there's tuner conflict. In that case, TvView will resets
   1600         // its current channel onConnectionFailed().
   1601         checkChannelLockNeeded(mTvView, channel);
   1602         if (updateChannelBanner) {
   1603             mOverlayManager.updateChannelBannerAndShowIfNeeded(
   1604                     TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
   1605         }
   1606         if (mActivityResumed) {
   1607             // requestVisibleBehind should be called after onResume() is called. But, when
   1608             // launcher is over the TV app and the screen is turned off and on, tune() can
   1609             // be called during the pause state by mBroadcastReceiver (Intent.ACTION_SCREEN_ON).
   1610             requestVisibleBehind(true);
   1611         }
   1612         mMediaSessionWrapper.update(mTvView.isBlocked(), getCurrentChannel(), getCurrentProgram());
   1613     }
   1614 
   1615     // Runs the runnable after the activity is attached to window to show the fragment transition
   1616     // animation.
   1617     // The runnable runs asynchronously to show the animation a little better even when system is
   1618     // busy at the moment it is called.
   1619     // If the activity is paused shortly, runnable may not be called because all the fragments
   1620     // should be closed when the activity is paused.
   1621     private void runAfterAttachedToWindow(final Runnable runnable) {
   1622         final Runnable runOnlyIfActivityIsResumed = new Runnable() {
   1623             @Override
   1624             public void run() {
   1625                 if (mActivityResumed) {
   1626                     runnable.run();
   1627                 }
   1628             }
   1629         };
   1630         if (mContentView.isAttachedToWindow()) {
   1631             mHandler.post(runOnlyIfActivityIsResumed);
   1632         } else {
   1633             mContentView.getViewTreeObserver().addOnWindowAttachListener(
   1634                     new ViewTreeObserver.OnWindowAttachListener() {
   1635                         @Override
   1636                         public void onWindowAttached() {
   1637                             mContentView.getViewTreeObserver().removeOnWindowAttachListener(this);
   1638                             mHandler.post(runOnlyIfActivityIsResumed);
   1639                         }
   1640 
   1641                         @Override
   1642                         public void onWindowDetached() { }
   1643                     });
   1644         }
   1645     }
   1646 
   1647     boolean isNowPlayingProgram(Channel channel, Program program) {
   1648         return program == null ? (channel != null && getCurrentProgram() == null
   1649                 && channel.equals(getCurrentChannel())) : program.equals(getCurrentProgram());
   1650     }
   1651 
   1652     private void addToRecentChannels(long channelId) {
   1653         if (!mRecentChannels.remove(channelId)) {
   1654             if (mRecentChannels.size() >= MAX_RECENT_CHANNELS) {
   1655                 mRecentChannels.removeLast();
   1656             }
   1657         }
   1658         mRecentChannels.addFirst(channelId);
   1659         mOverlayManager.getMenu().onRecentChannelsChanged();
   1660     }
   1661 
   1662     /**
   1663      * Returns the recently tuned channels.
   1664      */
   1665     public ArrayDeque<Long> getRecentChannels() {
   1666         return mRecentChannels;
   1667     }
   1668 
   1669     private void checkChannelLockNeeded(TunableTvView tvView, Channel currentChannel) {
   1670         if (currentChannel == null) {
   1671             currentChannel = tvView.getCurrentChannel();
   1672         }
   1673         if (tvView.isPlaying() && currentChannel != null) {
   1674             if (getParentalControlSettings().isParentalControlsEnabled()
   1675                     && currentChannel.isLocked()
   1676                     && !mShowLockedChannelsTemporarily
   1677                     && !(isUnderShrunkenTvView()
   1678                             && currentChannel.equals(mChannelBeforeShrunkenTvView)
   1679                             && mWasChannelUnblockedBeforeShrunkenByUser)) {
   1680                 if (DEBUG) Log.d(TAG, "Channel " + currentChannel.getId() + " is locked");
   1681                 blockOrUnblockScreen(tvView, true);
   1682             } else {
   1683                 blockOrUnblockScreen(tvView, false);
   1684             }
   1685         }
   1686     }
   1687 
   1688     private void blockOrUnblockScreen(TunableTvView tvView, boolean blockOrUnblock) {
   1689         tvView.blockOrUnblockScreen(blockOrUnblock);
   1690         if (tvView == mTvView) {
   1691             mOverlayManager.updateChannelBannerAndShowIfNeeded(
   1692                     TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
   1693             mMediaSessionWrapper.update(blockOrUnblock, getCurrentChannel(), getCurrentProgram());
   1694         }
   1695     }
   1696 
   1697     /**
   1698      * Hide the overlays when tuning to a channel from the menu (e.g. Channels).
   1699      */
   1700     public void hideOverlaysForTune() {
   1701         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE);
   1702     }
   1703 
   1704     public boolean needToKeepSetupScreenWhenHidingOverlay() {
   1705         return mInputIdUnderSetup != null && mIsSetupActivityCalledByPopup;
   1706     }
   1707 
   1708     // For now, this only takes care of 24fps.
   1709     private void applyDisplayRefreshRate(float videoFrameRate) {
   1710         boolean is24Fps = Math.abs(videoFrameRate - FRAME_RATE_FOR_FILM) < FRAME_RATE_EPSILON;
   1711         if (mIsFilmModeSet && !is24Fps) {
   1712             setPreferredRefreshRate(mDefaultRefreshRate);
   1713             mIsFilmModeSet = false;
   1714         } else if (!mIsFilmModeSet && is24Fps) {
   1715             DisplayManager displayManager = (DisplayManager) getSystemService(
   1716                     Context.DISPLAY_SERVICE);
   1717             Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
   1718 
   1719             float[] refreshRates = display.getSupportedRefreshRates();
   1720             for (float refreshRate : refreshRates) {
   1721                 // Be conservative and set only when the display refresh rate supports 24fps.
   1722                 if (Math.abs(videoFrameRate - refreshRate) < REFRESH_RATE_EPSILON) {
   1723                     setPreferredRefreshRate(refreshRate);
   1724                     mIsFilmModeSet = true;
   1725                     return;
   1726                 }
   1727             }
   1728         }
   1729     }
   1730 
   1731     private void setPreferredRefreshRate(float refreshRate) {
   1732         Window window = getWindow();
   1733         WindowManager.LayoutParams layoutParams = window.getAttributes();
   1734         layoutParams.preferredRefreshRate = refreshRate;
   1735         window.setAttributes(layoutParams);
   1736     }
   1737 
   1738     private void applyMultiAudio() {
   1739         List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO);
   1740         if (tracks == null) {
   1741             mTvOptionsManager.onMultiAudioChanged(null);
   1742             return;
   1743         }
   1744 
   1745         String id = TvSettings.getMultiAudioId(this);
   1746         String language = TvSettings.getMultiAudioLanguage(this);
   1747         int channelCount = TvSettings.getMultiAudioChannelCount(this);
   1748         TvTrackInfo bestTrack = TvTrackInfoUtils
   1749                 .getBestTrackInfo(tracks, id, language, channelCount);
   1750         if (bestTrack != null) {
   1751             String selectedTrack = getSelectedTrack(TvTrackInfo.TYPE_AUDIO);
   1752             if (!bestTrack.getId().equals(selectedTrack)) {
   1753                 selectTrack(TvTrackInfo.TYPE_AUDIO, bestTrack, UNDEFINED_TRACK_INDEX);
   1754             } else {
   1755                 mTvOptionsManager.onMultiAudioChanged(
   1756                         Utils.getMultiAudioString(this, bestTrack, false));
   1757             }
   1758             return;
   1759         }
   1760         mTvOptionsManager.onMultiAudioChanged(null);
   1761     }
   1762 
   1763     private void applyClosedCaption() {
   1764         List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE);
   1765         if (tracks == null) {
   1766             mTvOptionsManager.onClosedCaptionsChanged(null, UNDEFINED_TRACK_INDEX);
   1767             return;
   1768         }
   1769 
   1770         boolean enabled = mCaptionSettings.isEnabled();
   1771         mTvView.setClosedCaptionEnabled(enabled);
   1772 
   1773         String selectedTrackId = getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE);
   1774         TvTrackInfo alternativeTrack = null;
   1775         int alternativeTrackIndex = UNDEFINED_TRACK_INDEX;
   1776         if (enabled) {
   1777             String language = mCaptionSettings.getLanguage();
   1778             String trackId = mCaptionSettings.getTrackId();
   1779             for (int i = 0; i < tracks.size(); i++) {
   1780                 TvTrackInfo track = tracks.get(i);
   1781                 if (Utils.isEqualLanguage(track.getLanguage(), language)) {
   1782                     if (track.getId().equals(trackId)) {
   1783                         if (!track.getId().equals(selectedTrackId)) {
   1784                             selectTrack(TvTrackInfo.TYPE_SUBTITLE, track, i);
   1785                         } else {
   1786                             // Already selected. Update the option string only.
   1787                             mTvOptionsManager.onClosedCaptionsChanged(track, i);
   1788                         }
   1789                         if (DEBUG) {
   1790                             Log.d(TAG, "Subtitle Track Selected {id=" + track.getId()
   1791                                     + ", language=" + track.getLanguage() + "}");
   1792                         }
   1793                         return;
   1794                     } else if (alternativeTrack == null) {
   1795                         alternativeTrack = track;
   1796                         alternativeTrackIndex = i;
   1797                     }
   1798                 }
   1799             }
   1800             if (alternativeTrack != null) {
   1801                 if (!alternativeTrack.getId().equals(selectedTrackId)) {
   1802                     selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack, alternativeTrackIndex);
   1803                 } else {
   1804                     mTvOptionsManager
   1805                             .onClosedCaptionsChanged(alternativeTrack, alternativeTrackIndex);
   1806                 }
   1807                 if (DEBUG) {
   1808                     Log.d(TAG, "Subtitle Track Selected {id=" + alternativeTrack.getId()
   1809                             + ", language=" + alternativeTrack.getLanguage() + "}");
   1810                 }
   1811                 return;
   1812             }
   1813         }
   1814         if (selectedTrackId != null) {
   1815             selectTrack(TvTrackInfo.TYPE_SUBTITLE, null, UNDEFINED_TRACK_INDEX);
   1816             if (DEBUG) Log.d(TAG, "Subtitle Track Unselected");
   1817             return;
   1818         }
   1819         mTvOptionsManager.onClosedCaptionsChanged(null, UNDEFINED_TRACK_INDEX);
   1820     }
   1821 
   1822     public void showProgramGuideSearchFragment() {
   1823         getFragmentManager().beginTransaction().replace(R.id.fragment_container, mSearchFragment)
   1824                 .addToBackStack(null).commit();
   1825     }
   1826 
   1827     @Override
   1828     protected void onSaveInstanceState(Bundle outState) {
   1829         // Do not save instance state because restoring instance state when TV app died
   1830         // unexpectedly can cause some problems like initializing fragments duplicately and
   1831         // accessing resource before it is initialized.
   1832     }
   1833 
   1834     @Override
   1835     protected void onDestroy() {
   1836         if (DEBUG) Log.d(TAG, "onDestroy()");
   1837         Debug.getTimer(Debug.TAG_START_UP_TIMER).reset();
   1838         SideFragment.releaseRecycledViewPool();
   1839         ViewCache.getInstance().clear();
   1840         if (mTvView != null) {
   1841             mTvView.release();
   1842         }
   1843         if (mChannelTuner != null) {
   1844             mChannelTuner.removeListener(mChannelTunerListener);
   1845             mChannelTuner.stop();
   1846         }
   1847         TvApplication application = ((TvApplication) getApplication());
   1848         if (mProgramDataManager != null) {
   1849             mProgramDataManager.removeOnCurrentProgramUpdatedListener(
   1850                     Channel.INVALID_ID, mOnCurrentProgramUpdatedListener);
   1851             if (application.getMainActivityWrapper().isCurrent(this)) {
   1852                 mProgramDataManager.setPrefetchEnabled(false);
   1853             }
   1854         }
   1855         if (mOverlayManager != null) {
   1856             mOverlayManager.release();
   1857         }
   1858         mMemoryManageables.clear();
   1859         if (mMediaSessionWrapper != null) {
   1860             mMediaSessionWrapper.release();
   1861         }
   1862         if (mAudioManagerHelper != null) {
   1863             mAudioManagerHelper.release();
   1864         }
   1865         mHandler.removeCallbacksAndMessages(null);
   1866         application.getMainActivityWrapper().onMainActivityDestroyed(this);
   1867         if (mSendConfigInfoRecurringRunner != null) {
   1868             mSendConfigInfoRecurringRunner.stop();
   1869             mSendConfigInfoRecurringRunner = null;
   1870         }
   1871         if (mChannelStatusRecurringRunner != null) {
   1872             mChannelStatusRecurringRunner.stop();
   1873             mChannelStatusRecurringRunner = null;
   1874         }
   1875         if (mTvInputManagerHelper != null) {
   1876             mTvInputManagerHelper.clearTvInputLabels();
   1877             if (Features.TUNER.isEnabled(this)) {
   1878                 mTvInputManagerHelper.removeCallback(mTvInputCallback);
   1879             }
   1880         }
   1881         super.onDestroy();
   1882     }
   1883 
   1884     @Override
   1885     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1886         if (SystemProperties.LOG_KEYEVENT.getValue()) {
   1887             Log.d(TAG, "onKeyDown(" + keyCode + ", " + event + ")");
   1888         }
   1889         switch (mOverlayManager.onKeyDown(keyCode, event)) {
   1890             case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY:
   1891                 return super.onKeyDown(keyCode, event);
   1892             case KEY_EVENT_HANDLER_RESULT_HANDLED:
   1893                 return true;
   1894             case KEY_EVENT_HANDLER_RESULT_NOT_HANDLED:
   1895                 return false;
   1896             case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH:
   1897             default:
   1898                 // pass through
   1899         }
   1900         if (mSearchFragment.isVisible()) {
   1901             return super.onKeyDown(keyCode, event);
   1902         }
   1903         if (!mChannelTuner.areAllChannelsLoaded()) {
   1904             return false;
   1905         }
   1906         if (!mChannelTuner.isCurrentChannelPassthrough()) {
   1907             switch (keyCode) {
   1908                 case KeyEvent.KEYCODE_CHANNEL_UP:
   1909                 case KeyEvent.KEYCODE_DPAD_UP:
   1910                     if (event.getRepeatCount() == 0
   1911                             && mChannelTuner.getBrowsableChannelCount() > 0) {
   1912                         // message sending should be done before moving channel, because we use the
   1913                         // existence of message to decide if users are switching channel.
   1914                         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_UP_PRESSED,
   1915                                 System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
   1916                         moveToAdjacentChannel(true, false);
   1917                         mTracker.sendChannelUp();
   1918                     }
   1919                     return true;
   1920                 case KeyEvent.KEYCODE_CHANNEL_DOWN:
   1921                 case KeyEvent.KEYCODE_DPAD_DOWN:
   1922                     if (event.getRepeatCount() == 0
   1923                             && mChannelTuner.getBrowsableChannelCount() > 0) {
   1924                         // message sending should be done before moving channel, because we use the
   1925                         // existence of message to decide if users are switching channel.
   1926                         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_DOWN_PRESSED,
   1927                                 System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
   1928                         moveToAdjacentChannel(false, false);
   1929                         mTracker.sendChannelDown();
   1930                     }
   1931                     return true;
   1932             }
   1933         }
   1934         return super.onKeyDown(keyCode, event);
   1935     }
   1936 
   1937     @Override
   1938     public boolean onKeyUp(int keyCode, KeyEvent event) {
   1939         /*
   1940          * The following keyboard keys map to these remote keys or "debug actions"
   1941          *  - --------
   1942          *  A KEYCODE_MEDIA_AUDIO_TRACK
   1943          *  D debug: show debug options
   1944          *  E updateChannelBannerAndShowIfNeeded
   1945          *  G debug: refresh cloud epg
   1946          *  I KEYCODE_TV_INPUT
   1947          *  O debug: show display mode option
   1948          *  S KEYCODE_CAPTIONS: select subtitle
   1949          *  W debug: toggle screen size
   1950          *  V KEYCODE_MEDIA_RECORD debug: record the current channel for 30 sec
   1951          */
   1952         if (SystemProperties.LOG_KEYEVENT.getValue()) {
   1953             Log.d(TAG, "onKeyUp(" + keyCode + ", " + event + ")");
   1954         }
   1955         // If we are in the middle of channel change, finish it before showing overlays.
   1956         finishChannelChangeIfNeeded();
   1957 
   1958         if (event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) {
   1959             // Prevent MainActivity from being closed by onVisibleBehindCanceled()
   1960             mOtherActivityLaunched = true;
   1961             return false;
   1962         }
   1963         switch (mOverlayManager.onKeyUp(keyCode, event)) {
   1964             case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY:
   1965                 return super.onKeyUp(keyCode, event);
   1966             case KEY_EVENT_HANDLER_RESULT_HANDLED:
   1967                 return true;
   1968             case KEY_EVENT_HANDLER_RESULT_NOT_HANDLED:
   1969                 return false;
   1970             case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH:
   1971             default:
   1972                 // pass through
   1973         }
   1974         if (mSearchFragment.isVisible()) {
   1975             if (keyCode == KeyEvent.KEYCODE_BACK) {
   1976                 getFragmentManager().popBackStack();
   1977                 return true;
   1978             }
   1979             return super.onKeyUp(keyCode, event);
   1980         }
   1981         if (keyCode == KeyEvent.KEYCODE_BACK) {
   1982             // When the event is from onUnhandledInputEvent, onBackPressed is not automatically
   1983             // called. Therefore, we need to explicitly call onBackPressed().
   1984             onBackPressed();
   1985             return true;
   1986         }
   1987 
   1988         if (!mChannelTuner.areAllChannelsLoaded()) {
   1989             // Now channel map is under loading.
   1990         } else if (mChannelTuner.getBrowsableChannelCount() == 0) {
   1991             switch (keyCode) {
   1992                 case KeyEvent.KEYCODE_CHANNEL_UP:
   1993                 case KeyEvent.KEYCODE_DPAD_UP:
   1994                 case KeyEvent.KEYCODE_CHANNEL_DOWN:
   1995                 case KeyEvent.KEYCODE_DPAD_DOWN:
   1996                 case KeyEvent.KEYCODE_NUMPAD_ENTER:
   1997                 case KeyEvent.KEYCODE_DPAD_CENTER:
   1998                 case KeyEvent.KEYCODE_E:
   1999                 case KeyEvent.KEYCODE_MENU:
   2000                     showSettingsFragment();
   2001                     return true;
   2002             }
   2003         } else {
   2004             if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) {
   2005                 mOverlayManager.showKeypadChannelSwitch(keyCode);
   2006                 return true;
   2007             }
   2008             switch (keyCode) {
   2009                 case KeyEvent.KEYCODE_DPAD_RIGHT:
   2010                     if (!mTvView.isVideoOrAudioAvailable()
   2011                             && mTvView.getVideoUnavailableReason()
   2012                             == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE) {
   2013                         DvrUiHelper.startSchedulesActivityForTuneConflict(this,
   2014                                 mChannelTuner.getCurrentChannel());
   2015                         return true;
   2016                     }
   2017                     if (!PermissionUtils.hasModifyParentalControls(this)) {
   2018                         return true;
   2019                     }
   2020                     PinDialogFragment dialog = null;
   2021                     if (mTvView.isScreenBlocked()) {
   2022                         dialog = PinDialogFragment
   2023                                 .create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL);
   2024                     } else if (mTvView.isContentBlocked()) {
   2025                         dialog = PinDialogFragment
   2026                                 .create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM,
   2027                                         mTvView.getBlockedContentRating().flattenToString());
   2028                     }
   2029                     if (dialog != null) {
   2030                         mOverlayManager.showDialogFragment(PinDialogFragment.DIALOG_TAG, dialog,
   2031                                 false);
   2032                     }
   2033                     return true;
   2034                 case KeyEvent.KEYCODE_WINDOW:
   2035                     enterPictureInPictureMode();
   2036                     return true;
   2037                 case KeyEvent.KEYCODE_ENTER:
   2038                 case KeyEvent.KEYCODE_NUMPAD_ENTER:
   2039                 case KeyEvent.KEYCODE_E:
   2040                 case KeyEvent.KEYCODE_DPAD_CENTER:
   2041                 case KeyEvent.KEYCODE_MENU:
   2042                     if (event.isCanceled()) {
   2043                         // Ignore canceled key.
   2044                         // Note that if there's a TIS granted RECEIVE_INPUT_EVENT,
   2045                         // fallback keys not blacklisted will have FLAG_CANCELED.
   2046                         // See dispatchKeyEvent() for detail.
   2047                         return true;
   2048                     }
   2049                     if (keyCode != KeyEvent.KEYCODE_MENU) {
   2050                         mOverlayManager.updateChannelBannerAndShowIfNeeded(
   2051                                 TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
   2052                     }
   2053                     if (keyCode != KeyEvent.KEYCODE_E) {
   2054                         mOverlayManager.showMenu(Menu.REASON_NONE);
   2055                     }
   2056                     return true;
   2057                 case KeyEvent.KEYCODE_CHANNEL_UP:
   2058                 case KeyEvent.KEYCODE_DPAD_UP:
   2059                 case KeyEvent.KEYCODE_CHANNEL_DOWN:
   2060                 case KeyEvent.KEYCODE_DPAD_DOWN:
   2061                     // Channel change is already done in the head of this method.
   2062                     return true;
   2063                 case KeyEvent.KEYCODE_S:
   2064                     if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
   2065                         break;
   2066                     }
   2067                     // Pass through.
   2068                 case KeyEvent.KEYCODE_CAPTIONS: {
   2069                     mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment());
   2070                     return true;
   2071                 }
   2072                 case KeyEvent.KEYCODE_A:
   2073                     if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
   2074                         break;
   2075                     }
   2076                     // Pass through.
   2077                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
   2078                     mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment());
   2079                     return true;
   2080                 }
   2081                 case KeyEvent.KEYCODE_INFO: {
   2082                     mOverlayManager.showBanner();
   2083                     return true;
   2084                 }
   2085                 case KeyEvent.KEYCODE_MEDIA_RECORD:
   2086                 case KeyEvent.KEYCODE_V: {
   2087                     Channel currentChannel = getCurrentChannel();
   2088                     if (currentChannel != null && mDvrManager != null) {
   2089                         boolean isRecording =
   2090                                 mDvrManager.getCurrentRecording(currentChannel.getId()) != null;
   2091                         if (!isRecording) {
   2092                             if (!mDvrManager.isChannelRecordable(currentChannel)) {
   2093                                 Toast.makeText(this, R.string.dvr_msg_cannot_record_program,
   2094                                         Toast.LENGTH_SHORT).show();
   2095                             } else {
   2096                                 Program program = mProgramDataManager
   2097                                         .getCurrentProgram(currentChannel.getId());
   2098                                 DvrUiHelper.checkStorageStatusAndShowErrorMessage(this,
   2099                                         currentChannel.getInputId(), new Runnable() {
   2100                                             @Override
   2101                                             public void run() {
   2102                                                 DvrUiHelper.requestRecordingCurrentProgram(
   2103                                                         MainActivity.this,
   2104                                                         currentChannel, program, false);
   2105                                             }
   2106                                         });
   2107                             }
   2108                         } else {
   2109                             DvrUiHelper.showStopRecordingDialog(this, currentChannel.getId(),
   2110                                     DvrStopRecordingFragment.REASON_USER_STOP,
   2111                                     new HalfSizedDialogFragment.OnActionClickListener() {
   2112                                         @Override
   2113                                         public void onActionClick(long actionId) {
   2114                                             if (actionId == DvrStopRecordingFragment.ACTION_STOP) {
   2115                                                 ScheduledRecording currentRecording =
   2116                                                         mDvrManager.getCurrentRecording(
   2117                                                                 currentChannel.getId());
   2118                                                 if (currentRecording != null) {
   2119                                                     mDvrManager.stopRecording(currentRecording);
   2120                                                 }
   2121                                             }
   2122                                         }
   2123                                     });
   2124                         }
   2125                     }
   2126                     return true;
   2127                 }
   2128             }
   2129         }
   2130         if (keyCode == KeyEvent.KEYCODE_WINDOW) {
   2131             // Consumes the PIP button to prevent entering PIP mode
   2132             // in case that TV isn't showing properly (e.g. no browsable channel)
   2133             return true;
   2134         }
   2135         if (SystemProperties.USE_DEBUG_KEYS.getValue() || BuildConfig.ENG) {
   2136             switch (keyCode) {
   2137                 case KeyEvent.KEYCODE_W:
   2138                     mDebugNonFullSizeScreen = !mDebugNonFullSizeScreen;
   2139                     if (mDebugNonFullSizeScreen) {
   2140                         FrameLayout.LayoutParams params =
   2141                                 (FrameLayout.LayoutParams) mTvView.getLayoutParams();
   2142                         params.width = 960;
   2143                         params.height = 540;
   2144                         params.gravity = Gravity.START;
   2145                         mTvView.setTvViewLayoutParams(params);
   2146                     } else {
   2147                         FrameLayout.LayoutParams params =
   2148                                 (FrameLayout.LayoutParams) mTvView.getLayoutParams();
   2149                         params.width = ViewGroup.LayoutParams.MATCH_PARENT;
   2150                         params.height = ViewGroup.LayoutParams.MATCH_PARENT;
   2151                         params.gravity = Gravity.CENTER;
   2152                         mTvView.setTvViewLayoutParams(params);
   2153                     }
   2154                     return true;
   2155                 case KeyEvent.KEYCODE_CTRL_LEFT:
   2156                 case KeyEvent.KEYCODE_CTRL_RIGHT:
   2157                     mUseKeycodeBlacklist = !mUseKeycodeBlacklist;
   2158                     return true;
   2159                 case KeyEvent.KEYCODE_O:
   2160                     mOverlayManager.getSideFragmentManager().show(new DisplayModeFragment());
   2161                     return true;
   2162                 case KeyEvent.KEYCODE_D:
   2163                     mOverlayManager.getSideFragmentManager().show(new DeveloperOptionFragment());
   2164                     return true;
   2165             }
   2166         }
   2167         return super.onKeyUp(keyCode, event);
   2168     }
   2169 
   2170     @Override
   2171     public boolean onKeyLongPress(int keyCode, KeyEvent event) {
   2172         if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "onKeyLongPress(" + event);
   2173         if (USE_BACK_KEY_LONG_PRESS) {
   2174             // Treat the BACK key long press as the normal press since we changed the behavior in
   2175             // onBackPressed().
   2176             if (keyCode == KeyEvent.KEYCODE_BACK) {
   2177                 // It takes long time for TV app to finish, so stop TV first.
   2178                 stopAll(false);
   2179                 super.onBackPressed();
   2180                 return true;
   2181             }
   2182         }
   2183         return false;
   2184     }
   2185 
   2186     @Override
   2187     public void onUserInteraction() {
   2188         super.onUserInteraction();
   2189         if (mOverlayManager != null) {
   2190             mOverlayManager.onUserInteraction();
   2191         }
   2192     }
   2193 
   2194     @Override
   2195     public void enterPictureInPictureMode() {
   2196         // We need to hide overlay first, before moving the activity to PIP. If not, UI will
   2197         // be shown during PIP stack resizing, because UI and its animation is stuck during
   2198         // PIP resizing.
   2199         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
   2200         mHandler.post(new Runnable() {
   2201             @Override
   2202             public void run() {
   2203                 MainActivity.super.enterPictureInPictureMode();
   2204             }
   2205         });
   2206     }
   2207 
   2208     @Override
   2209     public void onWindowFocusChanged(boolean hasFocus) {
   2210         if (!hasFocus) {
   2211             finishChannelChangeIfNeeded();
   2212         }
   2213     }
   2214 
   2215     /**
   2216      * Returns {@code true} if one of the channel changing keys are pressed and not released yet.
   2217      */
   2218     public boolean isChannelChangeKeyDownReceived() {
   2219         return mHandler.hasMessages(MSG_CHANNEL_UP_PRESSED)
   2220                 || mHandler.hasMessages(MSG_CHANNEL_DOWN_PRESSED);
   2221     }
   2222 
   2223     private void finishChannelChangeIfNeeded() {
   2224         if (!isChannelChangeKeyDownReceived()) {
   2225             return;
   2226         }
   2227         mHandler.removeMessages(MSG_CHANNEL_UP_PRESSED);
   2228         mHandler.removeMessages(MSG_CHANNEL_DOWN_PRESSED);
   2229         if (mChannelTuner.getBrowsableChannelCount() > 0) {
   2230             if (!mTvView.isPlaying()) {
   2231                 // We expect that mTvView is already played. But, it is sometimes not.
   2232                 // TODO: we figure out the reason when mTvView is not played.
   2233                 Log.w(TAG, "TV view isn't played in finishChannelChangeIfNeeded");
   2234             }
   2235             tuneToChannel(mChannelTuner.getCurrentChannel());
   2236         } else {
   2237             showSettingsFragment();
   2238         }
   2239     }
   2240 
   2241     private boolean dispatchKeyEventToSession(final KeyEvent event) {
   2242         if (SystemProperties.LOG_KEYEVENT.getValue()) {
   2243             Log.d(TAG, "dispatchKeyEventToSession(" + event + ")");
   2244         }
   2245         boolean handled = false;
   2246         if (mTvView != null) {
   2247             handled = mTvView.dispatchKeyEvent(event);
   2248         }
   2249         if (isKeyEventBlocked()) {
   2250             if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK
   2251                     || event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B) && mNeedShowBackKeyGuide) {
   2252                 // KeyEvent.KEYCODE_BUTTON_B is also used like the back button.
   2253                 Toast.makeText(this, R.string.msg_back_key_guide, Toast.LENGTH_SHORT).show();
   2254                 mNeedShowBackKeyGuide = false;
   2255             }
   2256             return true;
   2257         }
   2258         return handled;
   2259     }
   2260 
   2261     private boolean isKeyEventBlocked() {
   2262         // If the current channel is a passthrough channel, we don't handle the key events in TV
   2263         // activity. Instead, the key event will be handled by the passthrough TV input.
   2264         return mChannelTuner.isCurrentChannelPassthrough();
   2265     }
   2266 
   2267     private void tuneToLastWatchedChannelForTunerInput() {
   2268         if (!mChannelTuner.isCurrentChannelPassthrough()) {
   2269             return;
   2270         }
   2271         stopTv();
   2272         startTv(null);
   2273     }
   2274 
   2275     public void tuneToChannel(Channel channel) {
   2276         if (channel == null) {
   2277             if (mTvView.isPlaying()) {
   2278                 mTvView.reset();
   2279             }
   2280         } else {
   2281             if (!mTvView.isPlaying()) {
   2282                 startTv(channel.getUri());
   2283             } else if (channel.equals(mTvView.getCurrentChannel())) {
   2284                 mOverlayManager.updateChannelBannerAndShowIfNeeded(
   2285                         TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
   2286             } else if (channel == mChannelTuner.getCurrentChannel()) {
   2287                 // Channel banner is already updated in moveToAdjacentChannel
   2288                 tune(false);
   2289             } else if (mChannelTuner.moveToChannel(channel)) {
   2290                 // Channel banner would be updated inside of tune.
   2291                 tune(true);
   2292             } else {
   2293                 showSettingsFragment();
   2294             }
   2295         }
   2296     }
   2297 
   2298     /**
   2299      * This method just moves the channel in the channel map and updates the channel banner,
   2300      * but doesn't actually tune to the channel.
   2301      * The caller of this method should call {@link #tune} in the end.
   2302      *
   2303      * @param channelUp {@code true} for channel up, and {@code false} for channel down.
   2304      * @param fastTuning {@code true} if fast tuning is requested.
   2305      */
   2306     private void moveToAdjacentChannel(boolean channelUp, boolean fastTuning) {
   2307         if (mChannelTuner.moveToAdjacentBrowsableChannel(channelUp)) {
   2308             mOverlayManager.updateChannelBannerAndShowIfNeeded(fastTuning ?
   2309                     TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST
   2310                     : TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
   2311         }
   2312     }
   2313 
   2314     /**
   2315      * Set the main TV view which holds HDMI-CEC active source based on the sound mode
   2316      */
   2317     private void restoreMainTvView() {
   2318         mTvView.setMain();
   2319     }
   2320 
   2321     @Override
   2322     public void onVisibleBehindCanceled() {
   2323         stopTv("onVisibleBehindCanceled()", false);
   2324         mTracker.sendScreenView("");
   2325         mAudioManagerHelper.abandonAudioFocus();
   2326         mMediaSessionWrapper.setPlaybackState(false);
   2327         mVisibleBehind = false;
   2328         if (!mOtherActivityLaunched && Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
   2329             // Workaround: in M, onStop is not called, even though it should be called after
   2330             // onVisibleBehindCanceled is called. As a workaround, we call finish().
   2331             finish();
   2332         }
   2333         super.onVisibleBehindCanceled();
   2334     }
   2335 
   2336     @Override
   2337     public void startActivityForResult(Intent intent, int requestCode) {
   2338         mOtherActivityLaunched = true;
   2339         if (intent.getCategories() == null
   2340                 || !intent.getCategories().contains(Intent.CATEGORY_HOME)) {
   2341             // Workaround b/30150267
   2342             requestVisibleBehind(false);
   2343         }
   2344         super.startActivityForResult(intent, requestCode);
   2345     }
   2346 
   2347     public List<TvTrackInfo> getTracks(int type) {
   2348         return mTvView.getTracks(type);
   2349     }
   2350 
   2351     public String getSelectedTrack(int type) {
   2352         return mTvView.getSelectedTrack(type);
   2353     }
   2354 
   2355     private void selectTrack(int type, TvTrackInfo track, int trackIndex) {
   2356         mTvView.selectTrack(type, track == null ? null : track.getId());
   2357         if (type == TvTrackInfo.TYPE_AUDIO) {
   2358             mTvOptionsManager.onMultiAudioChanged(track == null ? null :
   2359                     Utils.getMultiAudioString(this, track, false));
   2360         } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
   2361             mTvOptionsManager.onClosedCaptionsChanged(track, trackIndex);
   2362         }
   2363     }
   2364 
   2365     public void selectAudioTrack(String trackId) {
   2366         saveMultiAudioSetting(trackId);
   2367         applyMultiAudio();
   2368     }
   2369 
   2370     private void saveMultiAudioSetting(String trackId) {
   2371         List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO);
   2372         if (tracks != null) {
   2373             for (TvTrackInfo track : tracks) {
   2374                 if (track.getId().equals(trackId)) {
   2375                     TvSettings.setMultiAudioId(this, track.getId());
   2376                     TvSettings.setMultiAudioLanguage(this, track.getLanguage());
   2377                     TvSettings.setMultiAudioChannelCount(this, track.getAudioChannelCount());
   2378                     return;
   2379                 }
   2380             }
   2381         }
   2382         TvSettings.setMultiAudioId(this, null);
   2383         TvSettings.setMultiAudioLanguage(this, null);
   2384         TvSettings.setMultiAudioChannelCount(this, 0);
   2385     }
   2386 
   2387     public void selectSubtitleTrack(int option, String trackId) {
   2388         saveClosedCaptionSetting(option, trackId);
   2389         applyClosedCaption();
   2390     }
   2391 
   2392     public void selectSubtitleLanguage(int option, String language, String trackId) {
   2393         mCaptionSettings.setEnableOption(option);
   2394         mCaptionSettings.setLanguage(language);
   2395         mCaptionSettings.setTrackId(trackId);
   2396         applyClosedCaption();
   2397     }
   2398 
   2399     private void saveClosedCaptionSetting(int option, String trackId) {
   2400         mCaptionSettings.setEnableOption(option);
   2401         if (option == CaptionSettings.OPTION_ON) {
   2402             List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE);
   2403             if (tracks != null) {
   2404                 for (TvTrackInfo track : tracks) {
   2405                     if (track.getId().equals(trackId)) {
   2406                         mCaptionSettings.setLanguage(track.getLanguage());
   2407                         mCaptionSettings.setTrackId(trackId);
   2408                         return;
   2409                     }
   2410                 }
   2411             }
   2412         }
   2413     }
   2414 
   2415     private void updateAvailabilityToast() {
   2416         if (mTvView.isVideoAvailable()
   2417                 || mTvView.getCurrentChannel() != mChannelTuner.getCurrentChannel()) {
   2418             return;
   2419         }
   2420 
   2421         switch (mTvView.getVideoUnavailableReason()) {
   2422             case TunableTvView.VIDEO_UNAVAILABLE_REASON_NOT_TUNED:
   2423             case TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE:
   2424             case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
   2425             case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
   2426             case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:
   2427             case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
   2428                 return;
   2429             case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
   2430             default:
   2431                 Toast.makeText(this, R.string.msg_channel_unavailable_unknown,
   2432                         Toast.LENGTH_SHORT).show();
   2433                 break;
   2434         }
   2435     }
   2436 
   2437     /**
   2438      * Returns {@code true} if some overlay UI will be shown when the activity is resumed.
   2439      */
   2440     public boolean willShowOverlayUiWhenResume() {
   2441         return mInputToSetUp != null || mShowProgramGuide || mShowSelectInputView;
   2442     }
   2443 
   2444     /**
   2445      * Returns the current parental control settings.
   2446      */
   2447     public ParentalControlSettings getParentalControlSettings() {
   2448         return mTvInputManagerHelper.getParentalControlSettings();
   2449     }
   2450 
   2451     /**
   2452      * Returns a ContentRatingsManager instance.
   2453      */
   2454     public ContentRatingsManager getContentRatingsManager() {
   2455         return mTvInputManagerHelper.getContentRatingsManager();
   2456     }
   2457 
   2458     /**
   2459      * Returns the current captioning settings.
   2460      */
   2461     public CaptionSettings getCaptionSettings() {
   2462         return mCaptionSettings;
   2463     }
   2464 
   2465     /**
   2466      * Adds the {@link OnActionClickListener}.
   2467      */
   2468     public void addOnActionClickListener(OnActionClickListener listener) {
   2469         mOnActionClickListeners.add(listener);
   2470     }
   2471 
   2472     /**
   2473      * Removes the {@link OnActionClickListener}.
   2474      */
   2475     public void removeOnActionClickListener(OnActionClickListener listener) {
   2476         mOnActionClickListeners.remove(listener);
   2477     }
   2478 
   2479     @Override
   2480     public boolean onActionClick(String category, int actionId, Bundle params) {
   2481         // There should be only one action listener per an action.
   2482         for (OnActionClickListener l : mOnActionClickListeners) {
   2483             if (l.onActionClick(category, actionId, params)) {
   2484                 return true;
   2485             }
   2486         }
   2487         return false;
   2488     }
   2489 
   2490     // Initialize TV app for test. The setup process should be finished before the Live TV app is
   2491     // started. We only enable all the channels here.
   2492     private void initForTest() {
   2493         if (!TvCommonUtils.isRunningInTest()) {
   2494             return;
   2495         }
   2496 
   2497         Utils.enableAllChannels(this);
   2498     }
   2499 
   2500     // Lazy initialization
   2501     private void lazyInitializeIfNeeded() {
   2502         // Already initialized.
   2503         if (mLazyInitialized) {
   2504             return;
   2505         }
   2506         mLazyInitialized = true;
   2507         // Running initialization.
   2508         mHandler.postDelayed(new Runnable() {
   2509             @Override
   2510             public void run() {
   2511                 if (mActivityStarted) {
   2512                     initAnimations();
   2513                     initSideFragments();
   2514                     initMenuItemViews();
   2515                 }
   2516             }
   2517         }, LAZY_INITIALIZATION_DELAY);
   2518     }
   2519 
   2520     private void initAnimations() {
   2521         mTvViewUiManager.initAnimatorIfNeeded();
   2522         mOverlayManager.initAnimatorIfNeeded();
   2523     }
   2524 
   2525     private void initSideFragments() {
   2526         SideFragment.preloadItemViews(this);
   2527     }
   2528 
   2529     private void initMenuItemViews() {
   2530         mOverlayManager.getMenu().preloadItemViews();
   2531     }
   2532 
   2533     @Override
   2534     public void onTrimMemory(int level) {
   2535         super.onTrimMemory(level);
   2536         for (MemoryManageable memoryManageable : mMemoryManageables) {
   2537             memoryManageable.performTrimMemory(level);
   2538         }
   2539     }
   2540 
   2541     private static class MainActivityHandler extends WeakHandler<MainActivity> {
   2542         MainActivityHandler(MainActivity mainActivity) {
   2543             super(mainActivity);
   2544         }
   2545 
   2546         @Override
   2547         protected void handleMessage(Message msg, @NonNull MainActivity mainActivity) {
   2548             switch (msg.what) {
   2549                 case MSG_CHANNEL_DOWN_PRESSED:
   2550                     long startTime = (Long) msg.obj;
   2551                     // message re-sending should be done before moving channel, because we use the
   2552                     // existence of message to decide if users are switching channel.
   2553                     sendMessageDelayed(Message.obtain(msg), getDelay(startTime));
   2554                     mainActivity.moveToAdjacentChannel(false, true);
   2555                     break;
   2556                 case MSG_CHANNEL_UP_PRESSED:
   2557                     startTime = (Long) msg.obj;
   2558                     // message re-sending should be done before moving channel, because we use the
   2559                     // existence of message to decide if users are switching channel.
   2560                     sendMessageDelayed(Message.obtain(msg), getDelay(startTime));
   2561                     mainActivity.moveToAdjacentChannel(true, true);
   2562                     break;
   2563             }
   2564         }
   2565 
   2566         private long getDelay(long startTime) {
   2567             if (System.currentTimeMillis() - startTime > CHANNEL_CHANGE_NORMAL_SPEED_DURATION_MS) {
   2568                 return CHANNEL_CHANGE_DELAY_MS_IN_MAX_SPEED;
   2569             }
   2570             return CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED;
   2571         }
   2572     }
   2573 
   2574     private class MyOnTuneListener implements OnTuneListener {
   2575         boolean mUnlockAllowedRatingBeforeShrunken = true;
   2576         boolean mWasUnderShrunkenTvView;
   2577         Channel mChannel;
   2578 
   2579         private void onTune(Channel channel, boolean wasUnderShrukenTvView) {
   2580             Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.MyOnTuneListener.onTune");
   2581             mChannel = channel;
   2582             mWasUnderShrunkenTvView = wasUnderShrukenTvView;
   2583         }
   2584 
   2585         @Override
   2586         public void onUnexpectedStop(Channel channel) {
   2587             stopTv();
   2588             startTv(null);
   2589         }
   2590 
   2591         @Override
   2592         public void onTuneFailed(Channel channel) {
   2593             Log.w(TAG, "onTuneFailed(" + channel + ")");
   2594             if (mTvView.isFadedOut()) {
   2595                 mTvView.removeFadeEffect();
   2596             }
   2597             Toast.makeText(MainActivity.this, R.string.msg_channel_unavailable_unknown,
   2598                     Toast.LENGTH_SHORT).show();
   2599         }
   2600 
   2601         @Override
   2602         public void onStreamInfoChanged(StreamInfo info) {
   2603             if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) {
   2604                 mTracker.sendChannelTuneTime(info.getCurrentChannel(),
   2605                         mTuneDurationTimer.reset());
   2606             }
   2607             if (info.isVideoOrAudioAvailable() && mChannel == getCurrentChannel()) {
   2608                 mOverlayManager.updateChannelBannerAndShowIfNeeded(
   2609                         TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO);
   2610             }
   2611             applyDisplayRefreshRate(info.getVideoFrameRate());
   2612             mTvViewUiManager.updateTvAspectRatio();
   2613             applyMultiAudio();
   2614             applyClosedCaption();
   2615             mOverlayManager.getMenu().onStreamInfoChanged();
   2616             if (mTvView.isVideoAvailable()) {
   2617                 mTvViewUiManager.fadeInTvView();
   2618             }
   2619             if (!mTvView.isContentBlocked() && !mTvView.isScreenBlocked()) {
   2620                 updateAvailabilityToast();
   2621             }
   2622             mHandler.removeCallbacks(mRestoreMainViewRunnable);
   2623             restoreMainTvView();
   2624         }
   2625 
   2626         @Override
   2627         public void onChannelRetuned(Uri channel) {
   2628             if (channel == null) {
   2629                 return;
   2630             }
   2631             Channel currentChannel =
   2632                     mChannelDataManager.getChannel(ContentUris.parseId(channel));
   2633             if (currentChannel == null) {
   2634                 Log.e(TAG, "onChannelRetuned is called but can't find a channel with the URI "
   2635                         + channel);
   2636                 return;
   2637             }
   2638             if (isChannelChangeKeyDownReceived()) {
   2639                 // Ignore this message if the user is changing the channel.
   2640                 return;
   2641             }
   2642             mChannelTuner.setCurrentChannel(currentChannel);
   2643             mTvView.setCurrentChannel(currentChannel);
   2644             mOverlayManager.updateChannelBannerAndShowIfNeeded(
   2645                     TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
   2646         }
   2647 
   2648         @Override
   2649         public void onContentBlocked() {
   2650             Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
   2651                     "MainActivity.MyOnTuneListener.onContentBlocked removes timer");
   2652             Debug.removeTimer(Debug.TAG_START_UP_TIMER);
   2653             mTuneDurationTimer.reset();
   2654             TvContentRating rating = mTvView.getBlockedContentRating();
   2655             // When tuneTo was called while TV view was shrunken, if the channel id is the same
   2656             // with the channel watched before shrunken, we allow the rating which was allowed
   2657             // before.
   2658             if (mWasUnderShrunkenTvView && mUnlockAllowedRatingBeforeShrunken
   2659                     && mChannelBeforeShrunkenTvView.equals(mChannel)
   2660                     && rating.equals(mAllowedRatingBeforeShrunken)) {
   2661                 mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView();
   2662                 mTvView.unblockContent(rating);
   2663             }
   2664             mOverlayManager.setBlockingContentRating(rating);
   2665             mTvViewUiManager.fadeInTvView();
   2666             mMediaSessionWrapper.update(true, getCurrentChannel(), getCurrentProgram());
   2667         }
   2668 
   2669         @Override
   2670         public void onContentAllowed() {
   2671             if (!isUnderShrunkenTvView()) {
   2672                 mUnlockAllowedRatingBeforeShrunken = false;
   2673             }
   2674             mOverlayManager.setBlockingContentRating(null);
   2675             mMediaSessionWrapper.update(false, getCurrentChannel(), getCurrentProgram());
   2676         }
   2677     }
   2678 }
   2679