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