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