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