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