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