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