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