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