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