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