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