• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.hardware.display.DisplayManager;
31 import android.media.tv.TvContentRating;
32 import android.media.tv.TvContract;
33 import android.media.tv.TvContract.Channels;
34 import android.media.tv.TvInputInfo;
35 import android.media.tv.TvInputManager;
36 import android.media.tv.TvInputManager.TvInputCallback;
37 import android.media.tv.TvTrackInfo;
38 import android.media.tv.TvView.OnUnhandledInputEventListener;
39 import android.net.Uri;
40 import android.os.Build;
41 import android.os.Bundle;
42 import android.os.Handler;
43 import android.os.Message;
44 import android.os.PowerManager;
45 import android.provider.Settings;
46 import android.support.annotation.IntDef;
47 import android.support.annotation.NonNull;
48 import android.support.annotation.Nullable;
49 import android.text.TextUtils;
50 import android.util.ArraySet;
51 import android.util.Log;
52 import android.view.Display;
53 import android.view.Gravity;
54 import android.view.InputEvent;
55 import android.view.KeyEvent;
56 import android.view.View;
57 import android.view.ViewGroup;
58 import android.view.ViewTreeObserver;
59 import android.view.Window;
60 import android.view.WindowManager;
61 import android.view.accessibility.AccessibilityEvent;
62 import android.view.accessibility.AccessibilityManager;
63 import android.widget.FrameLayout;
64 import android.widget.Toast;
65 
66 import com.android.tv.analytics.SendChannelStatusRunnable;
67 import com.android.tv.analytics.SendConfigInfoRunnable;
68 import com.android.tv.analytics.Tracker;
69 import com.android.tv.common.BuildConfig;
70 import com.android.tv.common.MemoryManageable;
71 import com.android.tv.common.SoftPreconditions;
72 import com.android.tv.common.TvCommonUtils;
73 import com.android.tv.common.TvContentRatingCache;
74 import com.android.tv.common.WeakHandler;
75 import com.android.tv.common.feature.CommonFeatures;
76 import com.android.tv.common.ui.setup.OnActionClickListener;
77 import com.android.tv.data.Channel;
78 import com.android.tv.data.ChannelDataManager;
79 import com.android.tv.data.OnCurrentProgramUpdatedListener;
80 import com.android.tv.data.Program;
81 import com.android.tv.data.ProgramDataManager;
82 import com.android.tv.data.StreamInfo;
83 import com.android.tv.data.WatchedHistoryManager;
84 import com.android.tv.data.epg.EpgFetcher;
85 import com.android.tv.dialog.HalfSizedDialogFragment;
86 import com.android.tv.dialog.PinDialogFragment;
87 import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
88 import com.android.tv.dialog.SafeDismissDialogFragment;
89 import com.android.tv.dvr.DvrManager;
90 import com.android.tv.dvr.data.ScheduledRecording;
91 import com.android.tv.dvr.recorder.ConflictChecker;
92 import com.android.tv.dvr.ui.DvrStopRecordingFragment;
93 import com.android.tv.dvr.ui.DvrUiHelper;
94 import com.android.tv.menu.Menu;
95 import com.android.tv.onboarding.OnboardingActivity;
96 import com.android.tv.parental.ContentRatingsManager;
97 import com.android.tv.parental.ParentalControlSettings;
98 import com.android.tv.perf.EventNames;
99 import com.android.tv.perf.PerformanceMonitor;
100 import com.android.tv.perf.StubPerformanceMonitor;
101 import com.android.tv.perf.TimerEvent;
102 import com.android.tv.recommendation.ChannelPreviewUpdater;
103 import com.android.tv.recommendation.NotificationService;
104 import com.android.tv.search.ProgramGuideSearchFragment;
105 import com.android.tv.tuner.TunerInputController;
106 import com.android.tv.tuner.TunerPreferences;
107 import com.android.tv.tuner.setup.TunerSetupActivity;
108 import com.android.tv.tuner.tvinput.TunerTvInputService;
109 import com.android.tv.ui.ChannelBannerView;
110 import com.android.tv.ui.InputBannerView;
111 import com.android.tv.ui.KeypadChannelSwitchView;
112 import com.android.tv.ui.SelectInputView;
113 import com.android.tv.ui.SelectInputView.OnInputSelectedCallback;
114 import com.android.tv.ui.TunableTvView;
115 import com.android.tv.ui.TunableTvView.BlockScreenType;
116 import com.android.tv.ui.TunableTvView.OnTuneListener;
117 import com.android.tv.ui.TvOverlayManager;
118 import com.android.tv.ui.TvViewUiManager;
119 import com.android.tv.ui.sidepanel.ClosedCaptionFragment;
120 import com.android.tv.ui.sidepanel.CustomizeChannelListFragment;
121 import com.android.tv.ui.sidepanel.DeveloperOptionFragment;
122 import com.android.tv.ui.sidepanel.DisplayModeFragment;
123 import com.android.tv.ui.sidepanel.MultiAudioFragment;
124 import com.android.tv.ui.sidepanel.SettingsFragment;
125 import com.android.tv.ui.sidepanel.SideFragment;
126 import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment;
127 import com.android.tv.util.AccountHelper;
128 import com.android.tv.util.CaptionSettings;
129 import com.android.tv.util.Debug;
130 import com.android.tv.util.DurationTimer;
131 import com.android.tv.util.ImageCache;
132 import com.android.tv.util.OnboardingUtils;
133 import com.android.tv.util.PermissionUtils;
134 import com.android.tv.util.RecurringRunner;
135 import com.android.tv.util.SetupUtils;
136 import com.android.tv.util.SystemProperties;
137 import com.android.tv.util.TvInputManagerHelper;
138 import com.android.tv.util.TvSettings;
139 import com.android.tv.util.TvTrackInfoUtils;
140 import com.android.tv.util.Utils;
141 import com.android.tv.util.ViewCache;
142 
143 import java.lang.annotation.Retention;
144 import java.lang.annotation.RetentionPolicy;
145 import java.util.ArrayDeque;
146 import java.util.ArrayList;
147 import java.util.HashSet;
148 import java.util.List;
149 import java.util.Objects;
150 import java.util.Set;
151 import java.util.concurrent.TimeUnit;
152 
153 /**
154  * The main activity for the Live TV app.
155  */
156 public class MainActivity extends Activity implements OnActionClickListener, OnPinCheckedListener {
157     private static final String TAG = "MainActivity";
158     private static final boolean DEBUG = false;
159 
160     @Retention(RetentionPolicy.SOURCE)
161     @IntDef({KEY_EVENT_HANDLER_RESULT_PASSTHROUGH, KEY_EVENT_HANDLER_RESULT_NOT_HANDLED,
162         KEY_EVENT_HANDLER_RESULT_HANDLED, KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY})
163     public @interface KeyHandlerResultType {}
164     public static final int KEY_EVENT_HANDLER_RESULT_PASSTHROUGH = 0;
165     public static final int KEY_EVENT_HANDLER_RESULT_NOT_HANDLED = 1;
166     public static final int KEY_EVENT_HANDLER_RESULT_HANDLED = 2;
167     public static final int KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY = 3;
168 
169     private static final boolean USE_BACK_KEY_LONG_PRESS = false;
170 
171     private static final float FRAME_RATE_FOR_FILM = 23.976f;
172     private static final float FRAME_RATE_EPSILON = 0.1f;
173 
174 
175     private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1;
176     private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
177 
178     // Tracker screen names.
179     public static final String SCREEN_NAME = "Main";
180     private static final String SCREEN_BEHIND_NAME = "Behind";
181 
182     private static final float REFRESH_RATE_EPSILON = 0.01f;
183     private static final HashSet<Integer> BLACKLIST_KEYCODE_TO_TIS;
184     // These keys won't be passed to TIS in addition to gamepad buttons.
185     static {
186         BLACKLIST_KEYCODE_TO_TIS = new HashSet<>();
187         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_TV_INPUT);
188         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MENU);
189         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_UP);
190         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_DOWN);
191         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_UP);
192         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_DOWN);
193         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_MUTE);
194         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MUTE);
195         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_SEARCH);
196         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_WINDOW);
197     }
198 
199 
200     private static final IntentFilter SYSTEM_INTENT_FILTER = new IntentFilter();
201     static {
202         SYSTEM_INTENT_FILTER.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
203         SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF);
204         SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_ON);
205         SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED);
206     }
207 
208     private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
209 
210     private static final String KEY_INIT_CHANNEL_ID = "com.android.tv.init_channel_id";
211 
212     // Change channels with key long press.
213     private static final int CHANNEL_CHANGE_NORMAL_SPEED_DURATION_MS = 3000;
214     private static final int CHANNEL_CHANGE_DELAY_MS_IN_MAX_SPEED = 50;
215     private static final int CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED = 200;
216     private static final int CHANNEL_CHANGE_INITIAL_DELAY_MILLIS = 500;
217 
218     private static final int MSG_CHANNEL_DOWN_PRESSED = 1000;
219     private static final int MSG_CHANNEL_UP_PRESSED = 1001;
220 
221     private static final int TVVIEW_SET_MAIN_TIMEOUT_MS = 3000;
222 
223     // Lazy initialization.
224     // Delay 1 second in order not to interrupt the first tune.
225     private static final long LAZY_INITIALIZATION_DELAY = TimeUnit.SECONDS.toMillis(1);
226 
227     private static final int UNDEFINED_TRACK_INDEX = -1;
228     private static final long START_UP_TIMER_RESET_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(3);
229 
230     private AccessibilityManager mAccessibilityManager;
231     private ChannelDataManager mChannelDataManager;
232     private ProgramDataManager mProgramDataManager;
233     private TvInputManagerHelper mTvInputManagerHelper;
234     private ChannelTuner mChannelTuner;
235     private final TvOptionsManager mTvOptionsManager = new TvOptionsManager(this);
236     private TvViewUiManager mTvViewUiManager;
237     private TimeShiftManager mTimeShiftManager;
238     private Tracker mTracker;
239     private final DurationTimer mMainDurationTimer = new DurationTimer();
240     private final DurationTimer mTuneDurationTimer = new DurationTimer();
241     private DvrManager mDvrManager;
242     private ConflictChecker mDvrConflictChecker;
243 
244     private View mContentView;
245     private TunableTvView mTvView;
246     private Bundle mTuneParams;
247     @Nullable
248     private Uri mInitChannelUri;
249     @Nullable
250     private String mParentInputIdWhenScreenOff;
251     private boolean mScreenOffIntentReceived;
252     private boolean mShowProgramGuide;
253     private boolean mShowSelectInputView;
254     private TvInputInfo mInputToSetUp;
255     private final List<MemoryManageable> mMemoryManageables = new ArrayList<>();
256     private MediaSessionWrapper mMediaSessionWrapper;
257     private final MyOnTuneListener mOnTuneListener = new MyOnTuneListener();
258 
259     private String mInputIdUnderSetup;
260     private boolean mIsSetupActivityCalledByPopup;
261     private AudioManagerHelper mAudioManagerHelper;
262     private boolean mTunePending;
263     private boolean mDebugNonFullSizeScreen;
264     private boolean mActivityResumed;
265     private boolean mActivityStarted;
266     private boolean mShouldTuneToTunerChannel;
267     private boolean mUseKeycodeBlacklist;
268     private boolean mShowLockedChannelsTemporarily;
269     private boolean mBackKeyPressed;
270     private boolean mNeedShowBackKeyGuide;
271     private boolean mVisibleBehind;
272     private boolean mShowNewSourcesFragment = true;
273     private String mTunerInputId;
274     private boolean mOtherActivityLaunched;
275     private PerformanceMonitor mPerformanceMonitor;
276 
277     private boolean mIsFilmModeSet;
278     private float mDefaultRefreshRate;
279 
280     private TvOverlayManager mOverlayManager;
281 
282     // mIsCurrentChannelUnblockedByUser and mWasChannelUnblockedBeforeShrunkenByUser are used for
283     // keeping the channel unblocking status while TV view is shrunken.
284     private boolean mIsCurrentChannelUnblockedByUser;
285     private boolean mWasChannelUnblockedBeforeShrunkenByUser;
286     private Channel mChannelBeforeShrunkenTvView;
287     private boolean mIsCompletingShrunkenTvView;
288 
289     private TvContentRating mLastAllowedRatingForCurrentChannel;
290     private TvContentRating mAllowedRatingBeforeShrunken;
291 
292     private CaptionSettings mCaptionSettings;
293     // Lazy initialization
294     private boolean mLazyInitialized;
295 
296     private static final int MAX_RECENT_CHANNELS = 5;
297     private final ArrayDeque<Long> mRecentChannels = new ArrayDeque<>(MAX_RECENT_CHANNELS);
298 
299     private RecurringRunner mSendConfigInfoRecurringRunner;
300     private RecurringRunner mChannelStatusRecurringRunner;
301 
302     private final Handler mHandler = new MainActivityHandler(this);
303     private final Set<OnActionClickListener> mOnActionClickListeners = new ArraySet<>();
304 
305     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
306         @Override
307         public void onReceive(Context context, Intent intent) {
308             switch (intent.getAction()) {
309                 case Intent.ACTION_SCREEN_OFF:
310                     if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF");
311                     // We need to stop TvView, when the screen is turned off. If not and TIS uses
312                     // MediaPlayer, a device may not go to the sleep mode and audio can be heard,
313                     // because MediaPlayer keeps playing media by its wake lock.
314                     mScreenOffIntentReceived = true;
315                     markCurrentChannelDuringScreenOff();
316                     stopAll(true);
317                     break;
318                 case Intent.ACTION_SCREEN_ON:
319                     if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON");
320                     if (!mActivityResumed && mVisibleBehind) {
321                         // ACTION_SCREEN_ON is usually called after onResume. But, if media is
322                         // played under launcher with requestVisibleBehind(true), onResume will
323                         // not be called. In this case, we need to resume TvView explicitly.
324                         resumeTvIfNeeded();
325                     }
326                     break;
327                 case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED:
328                     if (DEBUG) Log.d(TAG, "Received parental control settings change");
329                     applyParentalControlSettings();
330                     checkChannelLockNeeded(mTvView, null);
331                     break;
332                 case Intent.ACTION_TIME_CHANGED:
333                     // Re-tune the current channel to prevent incorrect behavior of trick-play.
334                     // See: b/37393628
335                     if (mChannelTuner.getCurrentChannel() != null) {
336                         tune(true);
337                     }
338                     break;
339             }
340         }
341     };
342 
343     private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener =
344             new OnCurrentProgramUpdatedListener() {
345         @Override
346         public void onCurrentProgramUpdated(long channelId, Program program) {
347             // Do not update channel banner by this notification
348             // when the time shifting is available.
349             if (mTimeShiftManager.isAvailable()) {
350                 return;
351             }
352             Channel channel = mTvView.getCurrentChannel();
353             if (channel != null && channel.getId() == channelId) {
354                 mOverlayManager.updateChannelBannerAndShowIfNeeded(
355                         TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
356                 mMediaSessionWrapper.update(mTvView.isBlocked(), channel, program);
357             }
358         }
359     };
360 
361     private final ChannelTuner.Listener mChannelTunerListener =
362             new ChannelTuner.Listener() {
363                 @Override
364                 public void onLoadFinished() {
365                     Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
366                             "MainActivity.mChannelTunerListener.onLoadFinished");
367                     SetupUtils.getInstance(MainActivity.this).markNewChannelsBrowsable();
368                     if (mActivityResumed) {
369                         resumeTvIfNeeded();
370                     }
371                     mOverlayManager.onBrowsableChannelsUpdated();
372                 }
373 
374                 @Override
375                 public void onBrowsableChannelListChanged() {
376                     mOverlayManager.onBrowsableChannelsUpdated();
377                 }
378 
379                 @Override
380                 public void onCurrentChannelUnavailable(Channel channel) {
381                     if (mChannelTuner.moveToAdjacentBrowsableChannel(true)) {
382                         tune(true);
383                     } else {
384                         stopTv("onCurrentChannelUnavailable()", false);
385                     }
386                 }
387 
388                 @Override
389                 public void onChannelChanged(Channel previousChannel, Channel currentChannel) {}
390             };
391 
392     private final Runnable mRestoreMainViewRunnable = new Runnable() {
393         @Override
394         public void run() {
395             restoreMainTvView();
396         }
397     };
398     private ProgramGuideSearchFragment mSearchFragment;
399 
400     private final TvInputCallback mTvInputCallback = new TvInputCallback() {
401         @Override
402         public void onInputAdded(String inputId) {
403             if (Features.TUNER.isEnabled(MainActivity.this) && mTunerInputId.equals(inputId)
404                     && TunerPreferences.shouldShowSetupActivity(MainActivity.this)) {
405                 Intent intent = TunerSetupActivity.createSetupActivity(MainActivity.this);
406                 startActivity(intent);
407                 TunerPreferences.setShouldShowSetupActivity(MainActivity.this, false);
408                 SetupUtils.getInstance(MainActivity.this).markAsKnownInput(mTunerInputId);
409             }
410         }
411     };
412 
applyParentalControlSettings()413     private void applyParentalControlSettings() {
414         boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings()
415                 .isParentalControlsEnabled();
416         mTvView.onParentalControlChanged(parentalControlEnabled);
417         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
418             ChannelPreviewUpdater.getInstance(this).updatePreviewDataForChannelsImmediately();
419         }
420     }
421 
422     @Override
onCreate(Bundle savedInstanceState)423     protected void onCreate(Bundle savedInstanceState) {
424         TimerEvent timer = StubPerformanceMonitor.startBootstrapTimer();
425         DurationTimer startUpDebugTimer = Debug.getTimer(Debug.TAG_START_UP_TIMER);
426         if (!startUpDebugTimer.isStarted()
427                 || startUpDebugTimer.getDuration() > START_UP_TIMER_RESET_THRESHOLD_MS) {
428             // TvApplication can start by other reason before MainActivty is launched.
429             // In this case, we restart the timer.
430             startUpDebugTimer.start();
431         }
432         startUpDebugTimer.log("MainActivity.onCreate");
433         if (DEBUG) Log.d(TAG,"onCreate()");
434         TvApplication.setCurrentRunningProcess(this, true);
435         super.onCreate(savedInstanceState);
436         ApplicationSingletons applicationSingletons = TvApplication.getSingletons(this);
437         if (!applicationSingletons.getTvInputManagerHelper().hasTvInputManager()) {
438             Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
439             finishAndRemoveTask();
440             return;
441         }
442         mPerformanceMonitor = applicationSingletons.getPerformanceMonitor();
443 
444         TvApplication tvApplication = (TvApplication) getApplication();
445         mChannelDataManager = tvApplication.getChannelDataManager();
446         // In API 23, TvContract.isChannelUriForPassthroughInput is hidden.
447         boolean isPassthroughInput =
448                 TvContract.isChannelUriForPassthroughInput(getIntent().getData());
449         boolean tuneToPassthroughInput = Intent.ACTION_VIEW.equals(getIntent().getAction())
450                 && isPassthroughInput;
451         boolean channelLoadedAndNoChannelAvailable = mChannelDataManager.isDbLoadFinished()
452                 && mChannelDataManager.getChannelCount() <= 0;
453         if ((OnboardingUtils.isFirstRunWithCurrentVersion(this)
454                 || channelLoadedAndNoChannelAvailable)
455                 && !tuneToPassthroughInput
456                 && !TvCommonUtils.isRunningInTest()) {
457             startOnboardingActivity();
458             return;
459         }
460         setContentView(R.layout.activity_tv);
461         mProgramDataManager = tvApplication.getProgramDataManager();
462         mTvInputManagerHelper = tvApplication.getTvInputManagerHelper();
463         mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view);
464         mTvView.initialize(mProgramDataManager, mTvInputManagerHelper);
465         mTvView.setOnUnhandledInputEventListener(new OnUnhandledInputEventListener() {
466             @Override
467             public boolean onUnhandledInputEvent(InputEvent event) {
468                 if (isKeyEventBlocked()) {
469                     return true;
470                 }
471                 if (event instanceof KeyEvent) {
472                     KeyEvent keyEvent = (KeyEvent) event;
473                     if (keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.isLongPress()) {
474                         if (onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) {
475                             return true;
476                         }
477                     }
478                     if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
479                         return onKeyUp(keyEvent.getKeyCode(), keyEvent);
480                     } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
481                         return onKeyDown(keyEvent.getKeyCode(), keyEvent);
482                     }
483                 }
484                 return false;
485             }
486         });
487         long channelId = Utils.getLastWatchedChannelId(this);
488         String inputId = Utils.getLastWatchedTunerInputId(this);
489         if (!isPassthroughInput && inputId != null
490                 && channelId != Channel.INVALID_ID) {
491             mTvView.warmUpInput(inputId, TvContract.buildChannelUri(channelId));
492         }
493 
494         tvApplication.getMainActivityWrapper().onMainActivityCreated(this);
495         if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
496             Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show();
497         }
498         mTracker = tvApplication.getTracker();
499         if (Features.TUNER.isEnabled(this)) {
500             mTvInputManagerHelper.addCallback(mTvInputCallback);
501         }
502         mTunerInputId = TunerTvInputService.getInputId(this);
503         mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID,
504                 mOnCurrentProgramUpdatedListener);
505         mProgramDataManager.setPrefetchEnabled(true);
506         mChannelTuner = new ChannelTuner(mChannelDataManager, mTvInputManagerHelper);
507         mChannelTuner.addListener(mChannelTunerListener);
508         mChannelTuner.start();
509         mMemoryManageables.add(mProgramDataManager);
510         mMemoryManageables.add(ImageCache.getInstance());
511         mMemoryManageables.add(TvContentRatingCache.getInstance());
512         if (CommonFeatures.DVR.isEnabled(this)) {
513             mDvrManager = tvApplication.getDvrManager();
514         }
515         mTimeShiftManager = new TimeShiftManager(this, mTvView, mProgramDataManager, mTracker,
516                 new OnCurrentProgramUpdatedListener() {
517                     @Override
518                     public void onCurrentProgramUpdated(long channelId, Program program) {
519                         mMediaSessionWrapper.update(mTvView.isBlocked(), getCurrentChannel(),
520                                 program);
521                         switch (mTimeShiftManager.getLastActionId()) {
522                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND:
523                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD:
524                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS:
525                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT:
526                                 mOverlayManager.updateChannelBannerAndShowIfNeeded(
527                                         TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
528                                 break;
529                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE:
530                             case TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY:
531                             default:
532                                 mOverlayManager.updateChannelBannerAndShowIfNeeded(
533                                         TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
534                                 break;
535                         }
536                     }
537                 });
538 
539         DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
540         Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
541         mDefaultRefreshRate = display.getRefreshRate();
542 
543         if (!PermissionUtils.hasAccessWatchedHistory(this)) {
544             WatchedHistoryManager watchedHistoryManager = new WatchedHistoryManager(
545                     getApplicationContext());
546             watchedHistoryManager.start();
547             mTvView.setWatchedHistoryManager(watchedHistoryManager);
548         }
549         mTvViewUiManager = new TvViewUiManager(this, mTvView,
550                 (FrameLayout) findViewById(android.R.id.content), mTvOptionsManager);
551 
552         mContentView = findViewById(android.R.id.content);
553         ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container);
554         ChannelBannerView channelBannerView = (ChannelBannerView) getLayoutInflater().inflate(
555                 R.layout.channel_banner, sceneContainer, false);
556         KeypadChannelSwitchView keypadChannelSwitchView = (KeypadChannelSwitchView)
557                 getLayoutInflater().inflate(R.layout.keypad_channel_switch, sceneContainer, false);
558         InputBannerView inputBannerView = (InputBannerView) getLayoutInflater()
559                 .inflate(R.layout.input_banner, sceneContainer, false);
560         SelectInputView selectInputView = (SelectInputView) getLayoutInflater()
561                 .inflate(R.layout.select_input, sceneContainer, false);
562         selectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() {
563             @Override
564             public void onTunerInputSelected() {
565                 Channel currentChannel = mChannelTuner.getCurrentChannel();
566                 if (currentChannel != null && !currentChannel.isPassthrough()) {
567                     hideOverlays();
568                 } else {
569                     tuneToLastWatchedChannelForTunerInput();
570                 }
571             }
572 
573             @Override
574             public void onPassthroughInputSelected(@NonNull TvInputInfo input) {
575                 Channel currentChannel = mChannelTuner.getCurrentChannel();
576                 String currentInputId = currentChannel == null ? null : currentChannel.getInputId();
577                 if (TextUtils.equals(input.getId(), currentInputId)) {
578                     hideOverlays();
579                 } else {
580                     tuneToChannel(Channel.createPassthroughChannel(input.getId()));
581                 }
582             }
583 
584             private void hideOverlays() {
585                 getOverlayManager().hideOverlays(
586                         TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
587                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
588                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
589                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
590                         | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
591             }
592         });
593         mSearchFragment = new ProgramGuideSearchFragment();
594         mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTvView, mTvOptionsManager,
595                 keypadChannelSwitchView, channelBannerView, inputBannerView,
596                 selectInputView, sceneContainer, mSearchFragment);
597 
598         mAudioManagerHelper = new AudioManagerHelper(this, mTvView);
599         mMediaSessionWrapper = new MediaSessionWrapper(this);
600 
601         mTvViewUiManager.restoreDisplayMode(false);
602         if (!handleIntent(getIntent())) {
603             finish();
604             return;
605         }
606 
607         mAccessibilityManager =
608                 (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
609         mSendConfigInfoRecurringRunner = new RecurringRunner(this, TimeUnit.DAYS.toMillis(1),
610                 new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper), null);
611         mSendConfigInfoRecurringRunner.start();
612         mChannelStatusRecurringRunner = SendChannelStatusRunnable
613                 .startChannelStatusRecurringRunner(this, mTracker, mChannelDataManager);
614 
615         // To avoid not updating Rating systems when changing language.
616         mTvInputManagerHelper.getContentRatingsManager().update();
617         if (CommonFeatures.DVR.isEnabled(this)
618                 && Features.SHOW_UPCOMING_CONFLICT_DIALOG.isEnabled(this)) {
619             mDvrConflictChecker = new ConflictChecker(this);
620         }
621         initForTest();
622         Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onCreate end");
623         mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONCREATE);
624     }
625 
startOnboardingActivity()626     private void startOnboardingActivity() {
627         startActivity(OnboardingActivity.buildIntent(this, getIntent()));
628         finish();
629     }
630 
631     @Override
onConfigurationChanged(Configuration newConfig)632     public void onConfigurationChanged(Configuration newConfig) {
633         super.onConfigurationChanged(newConfig);
634         float density = getResources().getDisplayMetrics().density;
635         mTvViewUiManager.onConfigurationChanged((int) (newConfig.screenWidthDp * density),
636                 (int) (newConfig.screenHeightDp * density));
637     }
638 
639     @Override
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)640     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
641             @NonNull int[] grantResults) {
642         if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) {
643             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
644                 // Start reload of dependent data
645                 mChannelDataManager.reload();
646                 mProgramDataManager.reload();
647 
648                 // Restart live channels.
649                 Intent intent = getIntent();
650                 finish();
651                 startActivity(intent);
652             } else {
653                 Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied,
654                         Toast.LENGTH_LONG).show();
655                 finish();
656             }
657         }
658     }
659 
getDesiredBlockScreenType()660     @BlockScreenType private int getDesiredBlockScreenType() {
661         if (!mActivityResumed) {
662             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
663         }
664         if (isUnderShrunkenTvView()) {
665             return TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW;
666         }
667         if (mOverlayManager.needHideTextOnMainView()) {
668             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
669         }
670         SafeDismissDialogFragment currentDialog = mOverlayManager.getCurrentDialog();
671         if (currentDialog != null) {
672             // If PIN dialog is shown for unblocking the channel lock or content ratings lock,
673             // keeping the unlocking message is more natural instead of changing it.
674             if (currentDialog instanceof PinDialogFragment) {
675                 int type = ((PinDialogFragment) currentDialog).getType();
676                 if (type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL
677                         || type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) {
678                     return TunableTvView.BLOCK_SCREEN_TYPE_NORMAL;
679                 }
680             }
681             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
682         }
683         if (mOverlayManager.isSetupFragmentActive()
684                 || mOverlayManager.isNewSourcesFragmentActive()) {
685             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
686         }
687         return TunableTvView.BLOCK_SCREEN_TYPE_NORMAL;
688     }
689 
690     @Override
onNewIntent(Intent intent)691     protected void onNewIntent(Intent intent) {
692         if (DEBUG) Log.d(TAG,"onNewIntent(): " + intent);
693         if (mOverlayManager == null) {
694             // It's called before onCreate. The intent will be handled at onCreate. b/30725058
695             return;
696         }
697         mOverlayManager.getSideFragmentManager().hideAll(false);
698         if (!handleIntent(intent) && !mActivityStarted) {
699             // If the activity is stopped and not destroyed, finish the activity.
700             // Otherwise, just ignore the intent.
701             finish();
702         }
703     }
704 
705     @Override
onStart()706     protected void onStart() {
707         TimerEvent timer = mPerformanceMonitor.startTimer();
708         if (DEBUG) Log.d(TAG,"onStart()");
709         super.onStart();
710         mScreenOffIntentReceived = false;
711         mActivityStarted = true;
712         mTracker.sendMainStart();
713         mMainDurationTimer.start();
714 
715         applyParentalControlSettings();
716         registerReceiver(mBroadcastReceiver, SYSTEM_INTENT_FILTER);
717 
718         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
719             Intent notificationIntent = new Intent(this, NotificationService.class);
720             notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION);
721             startService(notificationIntent);
722         }
723         TunerInputController.executeNetworkTunerDiscoveryAsyncTask(this);
724 
725         EpgFetcher.getInstance(this).fetchImmediatelyIfNeeded();
726         mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONSTART);
727     }
728 
729     @Override
onResume()730     protected void onResume() {
731         TimerEvent timer = mPerformanceMonitor.startTimer();
732         Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume start");
733         if (DEBUG) Log.d(TAG, "onResume()");
734         super.onResume();
735         if (!PermissionUtils.hasAccessAllEpg(this)
736                 && checkSelfPermission(PERMISSION_READ_TV_LISTINGS)
737                     != PackageManager.PERMISSION_GRANTED) {
738             requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS},
739                     PERMISSIONS_REQUEST_READ_TV_LISTINGS);
740         }
741         mTracker.sendScreenView(SCREEN_NAME);
742 
743         SystemProperties.updateSystemProperties();
744         mNeedShowBackKeyGuide = true;
745         mActivityResumed = true;
746         mShowNewSourcesFragment = true;
747         mOtherActivityLaunched = false;
748         mAudioManagerHelper.requestAudioFocus();
749 
750         if (mTvView.isPlaying()) {
751             // Every time onResume() is called the activity will be assumed to not have requested
752             // visible behind.
753             requestVisibleBehind(true);
754         }
755         Set<String> failedScheduledRecordingInfoSet =
756                 Utils.getFailedScheduledRecordingInfoSet(getApplicationContext());
757         if (Utils.hasRecordingFailedReason(
758                 getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)
759                 && !failedScheduledRecordingInfoSet.isEmpty()) {
760             runAfterAttachedToWindow(new Runnable() {
761                 @Override
762                 public void run() {
763                     DvrUiHelper.showDvrInsufficientSpaceErrorDialog(MainActivity.this,
764                             failedScheduledRecordingInfoSet);
765                 }
766             });
767         }
768 
769         if (mChannelTuner.areAllChannelsLoaded()) {
770             SetupUtils.getInstance(this).markNewChannelsBrowsable();
771             resumeTvIfNeeded();
772         }
773         mOverlayManager.showMenuWithTimeShiftPauseIfNeeded();
774 
775         // NOTE: The following codes are related to pop up an overlay UI after resume. When
776         // the following code is changed, please modify willShowOverlayUiWhenResume() accordingly.
777         if (mInputToSetUp != null) {
778             startSetupActivity(mInputToSetUp, false);
779             mInputToSetUp = null;
780         } else if (mShowProgramGuide) {
781             mShowProgramGuide = false;
782             mHandler.post(new Runnable() {
783                 // This will delay the start of the animation until after the Live Channel app is
784                 // shown. Without this the animation is completed before it is actually visible on
785                 // the screen.
786                 @Override
787                 public void run() {
788                     mOverlayManager.showProgramGuide();
789                 }
790             });
791         } else if (mShowSelectInputView) {
792             mShowSelectInputView = false;
793             mHandler.post(new Runnable() {
794                 // mShowSelectInputView is true when the activity is started/resumed because the
795                 // TV_INPUT button was pressed in a different app.
796                 // This will delay the start of the animation until after the Live Channel app is
797                 // shown. Without this the animation is completed before it is actually visible on
798                 // the screen.
799                 @Override
800                 public void run() {
801                     mOverlayManager.showSelectInputView();
802                 }
803             });
804         }
805         if (mDvrConflictChecker != null) {
806             mDvrConflictChecker.start();
807         }
808         Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume end");
809         mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONRESUME);
810     }
811 
812     @Override
onPause()813     protected void onPause() {
814         if (DEBUG) Log.d(TAG, "onPause()");
815         if (mDvrConflictChecker != null) {
816             mDvrConflictChecker.stop();
817         }
818         finishChannelChangeIfNeeded();
819         mActivityResumed = false;
820         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_DEFAULT);
821         mTvView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_NO_UI);
822         mBackKeyPressed = false;
823         mShowLockedChannelsTemporarily = false;
824         mShouldTuneToTunerChannel = false;
825         if (!mVisibleBehind) {
826             mAudioManagerHelper.abandonAudioFocus();
827             mMediaSessionWrapper.setPlaybackState(false);
828             mTracker.sendScreenView("");
829         } else {
830             mTracker.sendScreenView(SCREEN_BEHIND_NAME);
831         }
832         super.onPause();
833     }
834 
835     /**
836      * Returns true if {@link #onResume} is called and {@link #onPause} is not called yet.
837      */
isActivityResumed()838     public boolean isActivityResumed() {
839         return mActivityResumed;
840     }
841 
842     /**
843      * Returns true if {@link #onStart} is called and {@link #onStop} is not called yet.
844      */
isActivityStarted()845     public boolean isActivityStarted() {
846         return mActivityStarted;
847     }
848 
849     @Override
requestVisibleBehind(boolean enable)850     public boolean requestVisibleBehind(boolean enable) {
851         boolean state = super.requestVisibleBehind(enable);
852         mVisibleBehind = state;
853         return state;
854     }
855 
856     @Override
onPinChecked(boolean checked, int type, String rating)857     public void onPinChecked(boolean checked, int type, String rating) {
858         if (checked) {
859             switch (type) {
860                 case PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL:
861                     blockOrUnblockScreen(mTvView, false);
862                     mIsCurrentChannelUnblockedByUser = true;
863                     break;
864                 case PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM:
865                     TvContentRating unblockedRating = TvContentRating.unflattenFromString(rating);
866                     mLastAllowedRatingForCurrentChannel = unblockedRating;
867                     mTvView.unblockContent(unblockedRating);
868                     break;
869                 case PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN:
870                     mOverlayManager.getSideFragmentManager()
871                             .show(new ParentalControlsFragment(), false);
872                     // Pass through.
873                 case PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN:
874                     mOverlayManager.getSideFragmentManager().showSidePanel(true);
875                     break;
876             }
877         } else if (type == PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN) {
878             mOverlayManager.getSideFragmentManager().hideAll(false);
879         }
880     }
881 
resumeTvIfNeeded()882     private void resumeTvIfNeeded() {
883         if (DEBUG) Log.d(TAG, "resumeTvIfNeeded()");
884         if (!mTvView.isPlaying() || mInitChannelUri != null
885                 || (mShouldTuneToTunerChannel && mChannelTuner.isCurrentChannelPassthrough())) {
886             if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
887                 // The target input may not be ready yet, especially, just after screen on.
888                 String inputId = mInitChannelUri.getPathSegments().get(1);
889                 TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(inputId);
890                 if (input == null) {
891                     input = mTvInputManagerHelper.getTvInputInfo(mParentInputIdWhenScreenOff);
892                     if (input == null) {
893                         SoftPreconditions.checkState(false, TAG, "Input disappear.");
894                         finish();
895                     } else {
896                         mInitChannelUri =
897                                 TvContract.buildChannelUriForPassthroughInput(input.getId());
898                     }
899                 }
900             }
901             mParentInputIdWhenScreenOff = null;
902             startTv(mInitChannelUri);
903             mInitChannelUri = null;
904         }
905         // Make sure TV app has the main TV view to handle the case that TvView is used in other
906         // application.
907         restoreMainTvView();
908         mTvView.setBlockScreenType(getDesiredBlockScreenType());
909     }
910 
startTv(Uri channelUri)911     private void startTv(Uri channelUri) {
912         if (DEBUG) Log.d(TAG, "startTv Uri=" + channelUri);
913         if ((channelUri == null || !TvContract.isChannelUriForPassthroughInput(channelUri))
914                 && mChannelTuner.isCurrentChannelPassthrough()) {
915             // For passthrough TV input, channelUri is always given. If TV app is launched
916             // by TV app icon in a launcher, channelUri is null. So if passthrough TV input
917             // is playing, we stop the passthrough TV input.
918             stopTv();
919         }
920         SoftPreconditions.checkState(TvContract.isChannelUriForPassthroughInput(channelUri)
921                 || mChannelTuner.areAllChannelsLoaded(),
922                 TAG, "startTV assumes that ChannelDataManager is already loaded.");
923         if (mTvView.isPlaying()) {
924             // TV has already started.
925             if (channelUri == null || channelUri.equals(mChannelTuner.getCurrentChannelUri())) {
926                 // Simply adjust the volume without tune.
927                 mAudioManagerHelper.setVolumeByAudioFocusStatus();
928                 return;
929             }
930             stopTv();
931         }
932         if (mChannelTuner.getCurrentChannel() != null) {
933             Log.w(TAG, "The current channel should be reset before");
934             mChannelTuner.resetCurrentChannel();
935         }
936         if (channelUri == null) {
937             // If any initial channel id is not given, remember the last channel the user watched.
938             long channelId = Utils.getLastWatchedChannelId(this);
939             if (channelId != Channel.INVALID_ID) {
940                 channelUri = TvContract.buildChannelUri(channelId);
941             }
942         }
943 
944         if (channelUri == null) {
945             mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
946         } else {
947             if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
948                 Channel channel = Channel.createPassthroughChannel(channelUri);
949                 mChannelTuner.moveToChannel(channel);
950             } else {
951                 long channelId = ContentUris.parseId(channelUri);
952                 Channel channel = mChannelDataManager.getChannel(channelId);
953                 if (channel == null || !mChannelTuner.moveToChannel(channel)) {
954                     mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
955                     Log.w(TAG, "The requested channel (id=" + channelId + ") doesn't exist. "
956                             + "The first channel will be tuned to.");
957                 }
958             }
959         }
960 
961         mTvView.start();
962         mAudioManagerHelper.setVolumeByAudioFocusStatus();
963         tune(true);
964     }
965 
966     @Override
onStop()967     protected void onStop() {
968         if (DEBUG) Log.d(TAG, "onStop()");
969         if (mScreenOffIntentReceived) {
970             mScreenOffIntentReceived = false;
971         } else {
972             PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
973             if (!powerManager.isInteractive()) {
974                 // We added to check isInteractive as well as SCREEN_OFF intent, because
975                 // calling timing of the intent SCREEN_OFF is not consistent. b/25953633.
976                 // If we verify that checking isInteractive is enough, we can remove the logic
977                 // for SCREEN_OFF intent.
978                 markCurrentChannelDuringScreenOff();
979             }
980         }
981         mActivityStarted = false;
982         stopAll(false);
983         unregisterReceiver(mBroadcastReceiver);
984         mTracker.sendMainStop(mMainDurationTimer.reset());
985         super.onStop();
986     }
987 
988     /**
989      * Handles screen off to keep the current channel for next screen on.
990      */
markCurrentChannelDuringScreenOff()991     private void markCurrentChannelDuringScreenOff() {
992         mInitChannelUri = mChannelTuner.getCurrentChannelUri();
993         if (mChannelTuner.isCurrentChannelPassthrough()) {
994             // When ACTION_SCREEN_OFF is invoked, some CEC devices may be already
995             // removed. So we need to get the input info from ChannelTuner instead of
996             // TvInputManagerHelper.
997             TvInputInfo input = mChannelTuner.getCurrentInputInfo();
998             mParentInputIdWhenScreenOff = input.getParentId();
999             if (DEBUG) Log.d(TAG, "Parent input: " + mParentInputIdWhenScreenOff);
1000         }
1001     }
1002 
stopAll(boolean keepVisibleBehind)1003     private void stopAll(boolean keepVisibleBehind) {
1004         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
1005         stopTv("stopAll()", keepVisibleBehind);
1006     }
1007 
getTvInputManagerHelper()1008     public TvInputManagerHelper getTvInputManagerHelper() {
1009         return mTvInputManagerHelper;
1010     }
1011 
1012     /**
1013      * Starts setup activity for the given input {@code input}.
1014      *
1015      * @param calledByPopup If true, startSetupActivity is invoked from the setup fragment.
1016      */
startSetupActivity(TvInputInfo input, boolean calledByPopup)1017     public void startSetupActivity(TvInputInfo input, boolean calledByPopup) {
1018         Intent intent = TvCommonUtils.createSetupIntent(input);
1019         if (intent == null) {
1020             Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT).show();
1021             return;
1022         }
1023         // Even though other app can handle the intent, the setup launched by Live channels
1024         // should go through Live channels SetupPassthroughActivity.
1025         intent.setComponent(new ComponentName(this, SetupPassthroughActivity.class));
1026         try {
1027             // Now we know that the user intends to set up this input. Grant permission for writing
1028             // EPG data.
1029             SetupUtils.grantEpgPermission(this, input.getServiceInfo().packageName);
1030 
1031             mInputIdUnderSetup = input.getId();
1032             mIsSetupActivityCalledByPopup = calledByPopup;
1033             // Call requestVisibleBehind(false) before starting other activity.
1034             // In Activity.requestVisibleBehind(false), this activity is scheduled to be stopped
1035             // immediately if other activity is about to start. And this activity is scheduled to
1036             // to be stopped again after onPause().
1037             stopTv("startSetupActivity()", false);
1038             startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
1039         } catch (ActivityNotFoundException e) {
1040             mInputIdUnderSetup = null;
1041             Toast.makeText(this, getString(R.string.msg_unable_to_start_setup_activity,
1042                     input.loadLabel(this)), Toast.LENGTH_SHORT).show();
1043             return;
1044         }
1045         if (calledByPopup) {
1046             mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
1047                     | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
1048         } else {
1049             mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
1050                     | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY);
1051         }
1052     }
1053 
hasCaptioningSettingsActivity()1054     public boolean hasCaptioningSettingsActivity() {
1055         return Utils.isIntentAvailable(this, new Intent(Settings.ACTION_CAPTIONING_SETTINGS));
1056     }
1057 
startSystemCaptioningSettingsActivity()1058     public void startSystemCaptioningSettingsActivity() {
1059         Intent intent = new Intent(Settings.ACTION_CAPTIONING_SETTINGS);
1060         try {
1061             startActivitySafe(intent);
1062         } catch (ActivityNotFoundException e) {
1063             Toast.makeText(this, getString(R.string.msg_unable_to_start_system_captioning_settings),
1064                     Toast.LENGTH_SHORT).show();
1065         }
1066     }
1067 
getChannelDataManager()1068     public ChannelDataManager getChannelDataManager() {
1069         return mChannelDataManager;
1070     }
1071 
getProgramDataManager()1072     public ProgramDataManager getProgramDataManager() {
1073         return mProgramDataManager;
1074     }
1075 
getTvOptionsManager()1076     public TvOptionsManager getTvOptionsManager() {
1077         return mTvOptionsManager;
1078     }
1079 
getTvViewUiManager()1080     public TvViewUiManager getTvViewUiManager() {
1081         return mTvViewUiManager;
1082     }
1083 
getTimeShiftManager()1084     public TimeShiftManager getTimeShiftManager() {
1085         return mTimeShiftManager;
1086     }
1087 
1088     /**
1089      * Returns the instance of {@link TvOverlayManager}.
1090      */
getOverlayManager()1091     public TvOverlayManager getOverlayManager() {
1092         return mOverlayManager;
1093     }
1094 
1095     /**
1096      * Returns the {@link ConflictChecker}.
1097      */
1098     @Nullable
getDvrConflictChecker()1099     public ConflictChecker getDvrConflictChecker() {
1100         return mDvrConflictChecker;
1101     }
1102 
getCurrentChannel()1103     public Channel getCurrentChannel() {
1104         return mChannelTuner.getCurrentChannel();
1105     }
1106 
getCurrentChannelId()1107     public long getCurrentChannelId() {
1108         return mChannelTuner.getCurrentChannelId();
1109     }
1110 
1111     /**
1112      * Returns the current program which the user is watching right now.<p>
1113      *
1114      * It might be a live program. If the time shifting is available, it can be a past program, too.
1115      */
getCurrentProgram()1116     public Program getCurrentProgram() {
1117         if (!isChannelChangeKeyDownReceived() && mTimeShiftManager.isAvailable()) {
1118             // We shouldn't get current program from TimeShiftManager during channel tunning
1119             return mTimeShiftManager.getCurrentProgram();
1120         }
1121         return mProgramDataManager.getCurrentProgram(getCurrentChannelId());
1122     }
1123 
1124     /**
1125      * Returns the current playing time in milliseconds.<p>
1126      *
1127      * If the time shifting is available, the time is the playing position of the program,
1128      * otherwise, the system current time.
1129      */
getCurrentPlayingPosition()1130     public long getCurrentPlayingPosition() {
1131         if (mTimeShiftManager.isAvailable()) {
1132             return mTimeShiftManager.getCurrentPositionMs();
1133         }
1134         return System.currentTimeMillis();
1135     }
1136 
getBrowsableChannel()1137     private Channel getBrowsableChannel() {
1138         Channel curChannel = mChannelTuner.getCurrentChannel();
1139         if (curChannel != null && curChannel.isBrowsable()) {
1140             return curChannel;
1141         } else {
1142             return mChannelTuner.getAdjacentBrowsableChannel(true);
1143         }
1144     }
1145 
1146     /**
1147      * Call {@link Activity#startActivity} in a safe way.
1148      *
1149      * @see LauncherActivity
1150      */
startActivitySafe(Intent intent)1151     public void startActivitySafe(Intent intent) {
1152         LauncherActivity.startActivitySafe(this, intent);
1153     }
1154 
1155     /**
1156      * Call {@link Activity#startActivityForResult} in a safe way.
1157      *
1158      * @see LauncherActivity
1159      */
startActivityForResultSafe(Intent intent, int requestCode)1160     private void startActivityForResultSafe(Intent intent, int requestCode) {
1161         LauncherActivity.startActivityForResultSafe(this, intent, requestCode);
1162     }
1163 
1164     /**
1165      * Show settings fragment.
1166      */
showSettingsFragment()1167     public void showSettingsFragment() {
1168         if (!mChannelTuner.areAllChannelsLoaded()) {
1169             // Show ChannelSourcesFragment only if all the channels are loaded.
1170             return;
1171         }
1172         mOverlayManager.getSideFragmentManager().show(new SettingsFragment());
1173     }
1174 
showMerchantCollection()1175     public void showMerchantCollection() {
1176         startActivitySafe(OnboardingUtils.ONLINE_STORE_INTENT);
1177     }
1178 
1179     /**
1180      * It is called when shrunken TvView is desired, such as EditChannelFragment and
1181      * ChannelsLockedFragment.
1182      */
startShrunkenTvView(boolean showLockedChannelsTemporarily, boolean willMainViewBeTunerInput)1183     public void startShrunkenTvView(boolean showLockedChannelsTemporarily,
1184             boolean willMainViewBeTunerInput) {
1185         mChannelBeforeShrunkenTvView = mTvView.getCurrentChannel();
1186         mWasChannelUnblockedBeforeShrunkenByUser = mIsCurrentChannelUnblockedByUser;
1187         mAllowedRatingBeforeShrunken = mLastAllowedRatingForCurrentChannel;
1188         mTvViewUiManager.startShrunkenTvView();
1189 
1190         if (showLockedChannelsTemporarily) {
1191             mShowLockedChannelsTemporarily = true;
1192             checkChannelLockNeeded(mTvView, null);
1193         }
1194 
1195         mTvView.setBlockScreenType(getDesiredBlockScreenType());
1196     }
1197 
1198     /**
1199      * It is called when shrunken TvView is no longer desired, such as EditChannelFragment and
1200      * ChannelsLockedFragment.
1201      */
endShrunkenTvView()1202     public void endShrunkenTvView() {
1203         mTvViewUiManager.endShrunkenTvView();
1204         mIsCompletingShrunkenTvView = true;
1205 
1206         Channel returnChannel = mChannelBeforeShrunkenTvView;
1207         if (returnChannel == null
1208                 || (!returnChannel.isPassthrough() && !returnChannel.isBrowsable())) {
1209             // Try to tune to the next best channel instead.
1210             returnChannel = getBrowsableChannel();
1211         }
1212         mShowLockedChannelsTemporarily = false;
1213 
1214         // The current channel is mTvView.getCurrentChannel() and need to tune to the returnChannel.
1215         if (!Objects.equals(mTvView.getCurrentChannel(), returnChannel)) {
1216             final Channel channel = returnChannel;
1217             Runnable tuneAction = new Runnable() {
1218                 @Override
1219                 public void run() {
1220                     tuneToChannel(channel);
1221                     if (mChannelBeforeShrunkenTvView == null
1222                             || !mChannelBeforeShrunkenTvView.equals(channel)) {
1223                         Utils.setLastWatchedChannel(MainActivity.this, channel);
1224                     }
1225                     mIsCompletingShrunkenTvView = false;
1226                     mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
1227                     mTvView.setBlockScreenType(getDesiredBlockScreenType());
1228                 }
1229             };
1230             mTvViewUiManager.fadeOutTvView(tuneAction);
1231             // Will automatically fade-in when video becomes available.
1232         } else {
1233             checkChannelLockNeeded(mTvView, null);
1234             mIsCompletingShrunkenTvView = false;
1235             mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
1236             mTvView.setBlockScreenType(getDesiredBlockScreenType());
1237         }
1238     }
1239 
isUnderShrunkenTvView()1240     private boolean isUnderShrunkenTvView() {
1241         return mTvViewUiManager.isUnderShrunkenTvView() || mIsCompletingShrunkenTvView;
1242     }
1243 
1244     /**
1245      * Returns {@code true} if the tunable tv view is blocked by resource conflict or by parental
1246      * control, otherwise {@code false}.
1247      */
isScreenBlockedByResourceConflictOrParentalControl()1248     public boolean isScreenBlockedByResourceConflictOrParentalControl() {
1249         return mTvView.getVideoUnavailableReason()
1250                 == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE || mTvView.isBlocked();
1251     }
1252 
1253     @Override
onActivityResult(int requestCode, int resultCode, Intent data)1254     public void onActivityResult(int requestCode, int resultCode, Intent data) {
1255         if (requestCode == REQUEST_CODE_START_SETUP_ACTIVITY) {
1256             if (resultCode == RESULT_OK) {
1257                 int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup);
1258                 String text;
1259                 if (count > 0) {
1260                     text = getResources().getQuantityString(R.plurals.msg_channel_added,
1261                             count, count);
1262                 } else {
1263                     text = getString(R.string.msg_no_channel_added);
1264                 }
1265                 Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
1266                 mInputIdUnderSetup = null;
1267                 if (mChannelTuner.getCurrentChannel() == null) {
1268                     mChannelTuner.moveToAdjacentBrowsableChannel(true);
1269                 }
1270                 if (mTunePending) {
1271                     tune(true);
1272                 }
1273             } else {
1274                 mInputIdUnderSetup = null;
1275             }
1276             if (!mIsSetupActivityCalledByPopup) {
1277                 mOverlayManager.getSideFragmentManager().showSidePanel(false);
1278             }
1279         }
1280         if (data != null) {
1281             String errorMessage = data.getStringExtra(LauncherActivity.ERROR_MESSAGE);
1282             if (!TextUtils.isEmpty(errorMessage)) {
1283                 Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
1284             }
1285         }
1286     }
1287 
1288     @Override
dispatchKeyEvent(KeyEvent event)1289     public boolean dispatchKeyEvent(KeyEvent event) {
1290         if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
1291         // If an activity is closed on a back key down event, back key down events with none zero
1292         // repeat count or a back key up event can be happened without the first back key down
1293         // event which should be ignored in this activity.
1294         if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
1295             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
1296                 mBackKeyPressed = true;
1297             }
1298             if (!mBackKeyPressed) {
1299                 return true;
1300             }
1301             if (event.getAction() == KeyEvent.ACTION_UP) {
1302                 mBackKeyPressed = false;
1303             }
1304         }
1305 
1306         // When side panel is closing, it has the focus.
1307         // Keep the focus, but just don't deliver the key events.
1308         if ((mContentView.hasFocusable() && !mOverlayManager.getSideFragmentManager().isHiding())
1309                 || mOverlayManager.getSideFragmentManager().isActive()) {
1310             return super.dispatchKeyEvent(event);
1311         }
1312         if (BLACKLIST_KEYCODE_TO_TIS.contains(event.getKeyCode())
1313                 || KeyEvent.isGamepadButton(event.getKeyCode())) {
1314             // If the event is in blacklisted or gamepad key, do not pass it to session.
1315             // Gamepad keys are blacklisted to support TV UIs and here's the detail.
1316             // If there's a TIS granted RECEIVE_INPUT_EVENT, TIF sends key events to TIS
1317             // and return immediately saying that the event is handled.
1318             // In this case, fallback key will be injected but with FLAG_CANCELED
1319             // while gamepads support DPAD_CENTER and BACK by fallback.
1320             // Since we don't expect that TIS want to handle gamepad buttons now,
1321             // blacklist gamepad buttons and wait for next fallback keys.
1322             // TODO: Need to consider other fallback keys (e.g. ESCAPE)
1323             return super.dispatchKeyEvent(event);
1324         }
1325         return dispatchKeyEventToSession(event) || super.dispatchKeyEvent(event);
1326     }
1327 
1328     /**
1329      * Notifies the key input focus is changed to the TV view.
1330      */
updateKeyInputFocus()1331     public void updateKeyInputFocus() {
1332         mHandler.post(new Runnable() {
1333             @Override
1334             public void run() {
1335                 mTvView.setBlockScreenType(getDesiredBlockScreenType());
1336             }
1337         });
1338     }
1339 
1340     // It should be called before onResume.
handleIntent(Intent intent)1341     private boolean handleIntent(Intent intent) {
1342         // Reset the closed caption settings when the activity is 1)created or 2) restarted.
1343         // And do not reset while TvView is playing.
1344         if (!mTvView.isPlaying()) {
1345             mCaptionSettings = new CaptionSettings(this);
1346         }
1347         mShouldTuneToTunerChannel = intent.getBooleanExtra(Utils.EXTRA_KEY_FROM_LAUNCHER, false);
1348         mInitChannelUri = null;
1349 
1350         String extraAction = intent.getStringExtra(Utils.EXTRA_KEY_ACTION);
1351         if (!TextUtils.isEmpty(extraAction)) {
1352             if (DEBUG) Log.d(TAG, "Got an extra action: " + extraAction);
1353             if (Utils.EXTRA_ACTION_SHOW_TV_INPUT.equals(extraAction)) {
1354                 String lastWatchedChannelUri = Utils.getLastWatchedChannelUri(this);
1355                 if (lastWatchedChannelUri != null) {
1356                     mInitChannelUri = Uri.parse(lastWatchedChannelUri);
1357                 }
1358                 mShowSelectInputView = true;
1359             }
1360         }
1361 
1362         if (TvInputManager.ACTION_SETUP_INPUTS.equals(intent.getAction())) {
1363             runAfterAttachedToWindow(new Runnable() {
1364                 @Override
1365                 public void run() {
1366                     mOverlayManager.showSetupFragment();
1367                 }
1368             });
1369         } else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
1370             Uri uri = intent.getData();
1371             if (Utils.isProgramsUri(uri)) {
1372                 // When the URI points to the programs (directory, not an individual item), go to
1373                 // the program guide. The intention here is to respond to
1374                 // "content://android.media.tv/program", not
1375                 // "content://android.media.tv/program/XXX".
1376                 // Later, we might want to add handling of individual programs too.
1377                 mShowProgramGuide = true;
1378                 return true;
1379             }
1380             // In case the channel is given explicitly, use it.
1381             mInitChannelUri = uri;
1382             if (DEBUG) Log.d(TAG, "ACTION_VIEW with " + mInitChannelUri);
1383             if (Channels.CONTENT_URI.equals(mInitChannelUri)) {
1384                 // Tune to default channel.
1385                 mInitChannelUri = null;
1386                 mShouldTuneToTunerChannel = true;
1387                 return true;
1388             }
1389             if ((!Utils.isChannelUriForOneChannel(mInitChannelUri)
1390                     && !Utils.isChannelUriForInput(mInitChannelUri))) {
1391                 Log.w(TAG, "Malformed channel uri " + mInitChannelUri
1392                         + " tuning to default instead");
1393                 mInitChannelUri = null;
1394                 return true;
1395             }
1396             mTuneParams = intent.getExtras();
1397             if (mTuneParams == null) {
1398                 mTuneParams = new Bundle();
1399             }
1400             if (Utils.isChannelUriForTunerInput(mInitChannelUri)) {
1401                 long channelId = ContentUris.parseId(mInitChannelUri);
1402                 mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId);
1403             } else if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
1404                 // If mInitChannelUri is for a passthrough TV input.
1405                 String inputId = mInitChannelUri.getPathSegments().get(1);
1406                 TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(inputId);
1407                 if (input == null) {
1408                     mInitChannelUri = null;
1409                     Toast.makeText(this, R.string.msg_no_specific_input, Toast.LENGTH_SHORT).show();
1410                     return false;
1411                 } else if (!input.isPassthroughInput()) {
1412                     mInitChannelUri = null;
1413                     Toast.makeText(this, R.string.msg_not_passthrough_input, Toast.LENGTH_SHORT)
1414                             .show();
1415                     return false;
1416                 }
1417             } else if (mInitChannelUri != null) {
1418                 // Handle the URI built by TvContract.buildChannelsUriForInput().
1419                 String inputId = mInitChannelUri.getQueryParameter("input");
1420                 long channelId = Utils.getLastWatchedChannelIdForInput(this, inputId);
1421                 if (channelId == Channel.INVALID_ID) {
1422                     String[] projection = { Channels._ID };
1423                     long time = System.currentTimeMillis();
1424                     try (Cursor cursor = getContentResolver().query(uri, projection,
1425                             null, null, null)) {
1426                         if (cursor != null && cursor.moveToNext()) {
1427                             channelId = cursor.getLong(0);
1428                         }
1429                     }
1430                     Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity queries DB for "
1431                             + "last channel check (" + (System.currentTimeMillis() - time) + "ms)");
1432                 }
1433                 if (channelId == Channel.INVALID_ID) {
1434                     // Couldn't find any channel probably because the input hasn't been set up.
1435                     // Try to set it up.
1436                     mInitChannelUri = null;
1437                     mInputToSetUp = mTvInputManagerHelper.getTvInputInfo(inputId);
1438                 } else {
1439                     mInitChannelUri = TvContract.buildChannelUri(channelId);
1440                     mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId);
1441                 }
1442             }
1443         }
1444         return true;
1445     }
1446 
stopTv()1447     private void stopTv() {
1448         stopTv(null, false);
1449     }
1450 
stopTv(String logForCaller, boolean keepVisibleBehind)1451     private void stopTv(String logForCaller, boolean keepVisibleBehind) {
1452         if (logForCaller != null) {
1453             Log.i(TAG, "stopTv is called at " + logForCaller + ".");
1454         } else {
1455             if (DEBUG) Log.d(TAG, "stopTv()");
1456         }
1457         if (mTvView.isPlaying()) {
1458             mTvView.stop();
1459             if (!keepVisibleBehind) {
1460                 requestVisibleBehind(false);
1461             }
1462             mAudioManagerHelper.abandonAudioFocus();
1463             mMediaSessionWrapper.setPlaybackState(false);
1464         }
1465         TvApplication.getSingletons(this).getMainActivityWrapper()
1466                 .notifyCurrentChannelChange(this, null);
1467         mChannelTuner.resetCurrentChannel();
1468         mTunePending = false;
1469     }
1470 
scheduleRestoreMainTvView()1471     private void scheduleRestoreMainTvView() {
1472         mHandler.removeCallbacks(mRestoreMainViewRunnable);
1473         mHandler.postDelayed(mRestoreMainViewRunnable, TVVIEW_SET_MAIN_TIMEOUT_MS);
1474     }
1475 
1476     /**
1477      * Says {@code text} when accessibility is turned on.
1478      */
sendAccessibilityText(String text)1479     private void sendAccessibilityText(String text) {
1480         if (mAccessibilityManager.isEnabled()) {
1481             AccessibilityEvent event = AccessibilityEvent.obtain();
1482             event.setClassName(getClass().getName());
1483             event.setPackageName(getPackageName());
1484             event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1485             event.getText().add(text);
1486             mAccessibilityManager.sendAccessibilityEvent(event);
1487         }
1488     }
1489 
tune(boolean updateChannelBanner)1490     private void tune(boolean updateChannelBanner) {
1491         if (DEBUG) Log.d(TAG, "tune()");
1492         mTuneDurationTimer.start();
1493 
1494         lazyInitializeIfNeeded();
1495 
1496         // Prerequisites to be able to tune.
1497         if (mInputIdUnderSetup != null) {
1498             mTunePending = true;
1499             return;
1500         }
1501         mTunePending = false;
1502         final Channel channel = mChannelTuner.getCurrentChannel();
1503         SoftPreconditions.checkState(channel != null);
1504         if (channel == null) {
1505             return;
1506         }
1507         if (!mChannelTuner.isCurrentChannelPassthrough()) {
1508             if (mTvInputManagerHelper.getTunerTvInputSize() == 0) {
1509                 Toast.makeText(this, R.string.msg_no_input, Toast.LENGTH_SHORT).show();
1510                 finish();
1511                 return;
1512             }
1513             SetupUtils setupUtils = SetupUtils.getInstance(this);
1514             if (setupUtils.isFirstTune()) {
1515                 if (!mChannelTuner.areAllChannelsLoaded()) {
1516                     // tune() will be called, once all channels are loaded.
1517                     stopTv("tune()", false);
1518                     return;
1519                 }
1520                 if (mChannelDataManager.getChannelCount() > 0) {
1521                     mOverlayManager.showIntroDialog();
1522                 } else {
1523                     startOnboardingActivity();
1524                     return;
1525                 }
1526             }
1527             mShowNewSourcesFragment = false;
1528             if (mChannelTuner.getBrowsableChannelCount() == 0
1529                     && mChannelDataManager.getChannelCount() > 0
1530                     && !mOverlayManager.getSideFragmentManager().isActive()) {
1531                 if (!mChannelTuner.areAllChannelsLoaded()) {
1532                     return;
1533                 }
1534                 if (mTvInputManagerHelper.getTunerTvInputSize() == 1) {
1535                     mOverlayManager.getSideFragmentManager().show(
1536                             new CustomizeChannelListFragment());
1537                 } else {
1538                     mOverlayManager.showSetupFragment();
1539                 }
1540                 return;
1541             }
1542             if (!TvCommonUtils.isRunningInTest() && mShowNewSourcesFragment
1543                     && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) {
1544                 // Show new channel sources fragment.
1545                 runAfterAttachedToWindow(new Runnable() {
1546                     @Override
1547                     public void run() {
1548                         mOverlayManager.runAfterOverlaysAreClosed(new Runnable() {
1549                             @Override
1550                             public void run() {
1551                                 mOverlayManager.showNewSourcesFragment();
1552                             }
1553                         });
1554                     }
1555                 });
1556             }
1557             setupUtils.onTuned();
1558             if (mTuneParams != null) {
1559                 Long initChannelId = mTuneParams.getLong(KEY_INIT_CHANNEL_ID);
1560                 if (initChannelId == channel.getId()) {
1561                     mTuneParams.remove(KEY_INIT_CHANNEL_ID);
1562                 } else {
1563                     mTuneParams = null;
1564                 }
1565             }
1566         }
1567 
1568         mIsCurrentChannelUnblockedByUser = false;
1569         if (!isUnderShrunkenTvView()) {
1570             mLastAllowedRatingForCurrentChannel = null;
1571         }
1572         // For every tune, we need to inform the tuned channel or input to a user,
1573         // if Talkback is turned on.
1574         sendAccessibilityText(!mChannelTuner.isCurrentChannelPassthrough() ?
1575                 Utils.loadLabel(this, mTvInputManagerHelper.getTvInputInfo(channel.getInputId()))
1576                 : channel.getDisplayText());
1577 
1578         boolean success = mTvView.tuneTo(channel, mTuneParams, mOnTuneListener);
1579         mOnTuneListener.onTune(channel, isUnderShrunkenTvView());
1580 
1581         mTuneParams = null;
1582         if (!success) {
1583             Toast.makeText(this, R.string.msg_tune_failed, Toast.LENGTH_SHORT).show();
1584             return;
1585         }
1586 
1587         // Explicitly make the TV view main to make the selected input an HDMI-CEC active source.
1588         mTvView.setMain();
1589         scheduleRestoreMainTvView();
1590         if (!isUnderShrunkenTvView()) {
1591             if (!channel.isPassthrough()) {
1592                 addToRecentChannels(channel.getId());
1593             }
1594             Utils.setLastWatchedChannel(this, channel);
1595             TvApplication.getSingletons(this).getMainActivityWrapper()
1596                     .notifyCurrentChannelChange(this, channel);
1597         }
1598         // We have to provide channel here instead of using TvView's channel, because TvView's
1599         // channel might be null when there's tuner conflict. In that case, TvView will resets
1600         // its current channel onConnectionFailed().
1601         checkChannelLockNeeded(mTvView, channel);
1602         if (updateChannelBanner) {
1603             mOverlayManager.updateChannelBannerAndShowIfNeeded(
1604                     TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
1605         }
1606         if (mActivityResumed) {
1607             // requestVisibleBehind should be called after onResume() is called. But, when
1608             // launcher is over the TV app and the screen is turned off and on, tune() can
1609             // be called during the pause state by mBroadcastReceiver (Intent.ACTION_SCREEN_ON).
1610             requestVisibleBehind(true);
1611         }
1612         mMediaSessionWrapper.update(mTvView.isBlocked(), getCurrentChannel(), getCurrentProgram());
1613     }
1614 
1615     // Runs the runnable after the activity is attached to window to show the fragment transition
1616     // animation.
1617     // The runnable runs asynchronously to show the animation a little better even when system is
1618     // busy at the moment it is called.
1619     // If the activity is paused shortly, runnable may not be called because all the fragments
1620     // should be closed when the activity is paused.
runAfterAttachedToWindow(final Runnable runnable)1621     private void runAfterAttachedToWindow(final Runnable runnable) {
1622         final Runnable runOnlyIfActivityIsResumed = new Runnable() {
1623             @Override
1624             public void run() {
1625                 if (mActivityResumed) {
1626                     runnable.run();
1627                 }
1628             }
1629         };
1630         if (mContentView.isAttachedToWindow()) {
1631             mHandler.post(runOnlyIfActivityIsResumed);
1632         } else {
1633             mContentView.getViewTreeObserver().addOnWindowAttachListener(
1634                     new ViewTreeObserver.OnWindowAttachListener() {
1635                         @Override
1636                         public void onWindowAttached() {
1637                             mContentView.getViewTreeObserver().removeOnWindowAttachListener(this);
1638                             mHandler.post(runOnlyIfActivityIsResumed);
1639                         }
1640 
1641                         @Override
1642                         public void onWindowDetached() { }
1643                     });
1644         }
1645     }
1646 
isNowPlayingProgram(Channel channel, Program program)1647     boolean isNowPlayingProgram(Channel channel, Program program) {
1648         return program == null ? (channel != null && getCurrentProgram() == null
1649                 && channel.equals(getCurrentChannel())) : program.equals(getCurrentProgram());
1650     }
1651 
addToRecentChannels(long channelId)1652     private void addToRecentChannels(long channelId) {
1653         if (!mRecentChannels.remove(channelId)) {
1654             if (mRecentChannels.size() >= MAX_RECENT_CHANNELS) {
1655                 mRecentChannels.removeLast();
1656             }
1657         }
1658         mRecentChannels.addFirst(channelId);
1659         mOverlayManager.getMenu().onRecentChannelsChanged();
1660     }
1661 
1662     /**
1663      * Returns the recently tuned channels.
1664      */
getRecentChannels()1665     public ArrayDeque<Long> getRecentChannels() {
1666         return mRecentChannels;
1667     }
1668 
checkChannelLockNeeded(TunableTvView tvView, Channel currentChannel)1669     private void checkChannelLockNeeded(TunableTvView tvView, Channel currentChannel) {
1670         if (currentChannel == null) {
1671             currentChannel = tvView.getCurrentChannel();
1672         }
1673         if (tvView.isPlaying() && currentChannel != null) {
1674             if (getParentalControlSettings().isParentalControlsEnabled()
1675                     && currentChannel.isLocked()
1676                     && !mShowLockedChannelsTemporarily
1677                     && !(isUnderShrunkenTvView()
1678                             && currentChannel.equals(mChannelBeforeShrunkenTvView)
1679                             && mWasChannelUnblockedBeforeShrunkenByUser)) {
1680                 if (DEBUG) Log.d(TAG, "Channel " + currentChannel.getId() + " is locked");
1681                 blockOrUnblockScreen(tvView, true);
1682             } else {
1683                 blockOrUnblockScreen(tvView, false);
1684             }
1685         }
1686     }
1687 
blockOrUnblockScreen(TunableTvView tvView, boolean blockOrUnblock)1688     private void blockOrUnblockScreen(TunableTvView tvView, boolean blockOrUnblock) {
1689         tvView.blockOrUnblockScreen(blockOrUnblock);
1690         if (tvView == mTvView) {
1691             mOverlayManager.updateChannelBannerAndShowIfNeeded(
1692                     TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
1693             mMediaSessionWrapper.update(blockOrUnblock, getCurrentChannel(), getCurrentProgram());
1694         }
1695     }
1696 
1697     /**
1698      * Hide the overlays when tuning to a channel from the menu (e.g. Channels).
1699      */
hideOverlaysForTune()1700     public void hideOverlaysForTune() {
1701         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE);
1702     }
1703 
needToKeepSetupScreenWhenHidingOverlay()1704     public boolean needToKeepSetupScreenWhenHidingOverlay() {
1705         return mInputIdUnderSetup != null && mIsSetupActivityCalledByPopup;
1706     }
1707 
1708     // For now, this only takes care of 24fps.
applyDisplayRefreshRate(float videoFrameRate)1709     private void applyDisplayRefreshRate(float videoFrameRate) {
1710         boolean is24Fps = Math.abs(videoFrameRate - FRAME_RATE_FOR_FILM) < FRAME_RATE_EPSILON;
1711         if (mIsFilmModeSet && !is24Fps) {
1712             setPreferredRefreshRate(mDefaultRefreshRate);
1713             mIsFilmModeSet = false;
1714         } else if (!mIsFilmModeSet && is24Fps) {
1715             DisplayManager displayManager = (DisplayManager) getSystemService(
1716                     Context.DISPLAY_SERVICE);
1717             Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1718 
1719             float[] refreshRates = display.getSupportedRefreshRates();
1720             for (float refreshRate : refreshRates) {
1721                 // Be conservative and set only when the display refresh rate supports 24fps.
1722                 if (Math.abs(videoFrameRate - refreshRate) < REFRESH_RATE_EPSILON) {
1723                     setPreferredRefreshRate(refreshRate);
1724                     mIsFilmModeSet = true;
1725                     return;
1726                 }
1727             }
1728         }
1729     }
1730 
1731     private void setPreferredRefreshRate(float refreshRate) {
1732         Window window = getWindow();
1733         WindowManager.LayoutParams layoutParams = window.getAttributes();
1734         layoutParams.preferredRefreshRate = refreshRate;
1735         window.setAttributes(layoutParams);
1736     }
1737 
1738     private void applyMultiAudio() {
1739         List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO);
1740         if (tracks == null) {
1741             mTvOptionsManager.onMultiAudioChanged(null);
1742             return;
1743         }
1744 
1745         String id = TvSettings.getMultiAudioId(this);
1746         String language = TvSettings.getMultiAudioLanguage(this);
1747         int channelCount = TvSettings.getMultiAudioChannelCount(this);
1748         TvTrackInfo bestTrack = TvTrackInfoUtils
1749                 .getBestTrackInfo(tracks, id, language, channelCount);
1750         if (bestTrack != null) {
1751             String selectedTrack = getSelectedTrack(TvTrackInfo.TYPE_AUDIO);
1752             if (!bestTrack.getId().equals(selectedTrack)) {
1753                 selectTrack(TvTrackInfo.TYPE_AUDIO, bestTrack, UNDEFINED_TRACK_INDEX);
1754             } else {
1755                 mTvOptionsManager.onMultiAudioChanged(
1756                         Utils.getMultiAudioString(this, bestTrack, false));
1757             }
1758             return;
1759         }
1760         mTvOptionsManager.onMultiAudioChanged(null);
1761     }
1762 
1763     private void applyClosedCaption() {
1764         List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE);
1765         if (tracks == null) {
1766             mTvOptionsManager.onClosedCaptionsChanged(null, UNDEFINED_TRACK_INDEX);
1767             return;
1768         }
1769 
1770         boolean enabled = mCaptionSettings.isEnabled();
1771         mTvView.setClosedCaptionEnabled(enabled);
1772 
1773         String selectedTrackId = getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE);
1774         TvTrackInfo alternativeTrack = null;
1775         int alternativeTrackIndex = UNDEFINED_TRACK_INDEX;
1776         if (enabled) {
1777             String language = mCaptionSettings.getLanguage();
1778             String trackId = mCaptionSettings.getTrackId();
1779             for (int i = 0; i < tracks.size(); i++) {
1780                 TvTrackInfo track = tracks.get(i);
1781                 if (Utils.isEqualLanguage(track.getLanguage(), language)) {
1782                     if (track.getId().equals(trackId)) {
1783                         if (!track.getId().equals(selectedTrackId)) {
1784                             selectTrack(TvTrackInfo.TYPE_SUBTITLE, track, i);
1785                         } else {
1786                             // Already selected. Update the option string only.
1787                             mTvOptionsManager.onClosedCaptionsChanged(track, i);
1788                         }
1789                         if (DEBUG) {
1790                             Log.d(TAG, "Subtitle Track Selected {id=" + track.getId()
1791                                     + ", language=" + track.getLanguage() + "}");
1792                         }
1793                         return;
1794                     } else if (alternativeTrack == null) {
1795                         alternativeTrack = track;
1796                         alternativeTrackIndex = i;
1797                     }
1798                 }
1799             }
1800             if (alternativeTrack != null) {
1801                 if (!alternativeTrack.getId().equals(selectedTrackId)) {
1802                     selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack, alternativeTrackIndex);
1803                 } else {
1804                     mTvOptionsManager
1805                             .onClosedCaptionsChanged(alternativeTrack, alternativeTrackIndex);
1806                 }
1807                 if (DEBUG) {
1808                     Log.d(TAG, "Subtitle Track Selected {id=" + alternativeTrack.getId()
1809                             + ", language=" + alternativeTrack.getLanguage() + "}");
1810                 }
1811                 return;
1812             }
1813         }
1814         if (selectedTrackId != null) {
1815             selectTrack(TvTrackInfo.TYPE_SUBTITLE, null, UNDEFINED_TRACK_INDEX);
1816             if (DEBUG) Log.d(TAG, "Subtitle Track Unselected");
1817             return;
1818         }
1819         mTvOptionsManager.onClosedCaptionsChanged(null, UNDEFINED_TRACK_INDEX);
1820     }
1821 
1822     public void showProgramGuideSearchFragment() {
1823         getFragmentManager().beginTransaction().replace(R.id.fragment_container, mSearchFragment)
1824                 .addToBackStack(null).commit();
1825     }
1826 
1827     @Override
1828     protected void onSaveInstanceState(Bundle outState) {
1829         // Do not save instance state because restoring instance state when TV app died
1830         // unexpectedly can cause some problems like initializing fragments duplicately and
1831         // accessing resource before it is initialized.
1832     }
1833 
1834     @Override
1835     protected void onDestroy() {
1836         if (DEBUG) Log.d(TAG, "onDestroy()");
1837         Debug.getTimer(Debug.TAG_START_UP_TIMER).reset();
1838         SideFragment.releaseRecycledViewPool();
1839         ViewCache.getInstance().clear();
1840         if (mTvView != null) {
1841             mTvView.release();
1842         }
1843         if (mChannelTuner != null) {
1844             mChannelTuner.removeListener(mChannelTunerListener);
1845             mChannelTuner.stop();
1846         }
1847         TvApplication application = ((TvApplication) getApplication());
1848         if (mProgramDataManager != null) {
1849             mProgramDataManager.removeOnCurrentProgramUpdatedListener(
1850                     Channel.INVALID_ID, mOnCurrentProgramUpdatedListener);
1851             if (application.getMainActivityWrapper().isCurrent(this)) {
1852                 mProgramDataManager.setPrefetchEnabled(false);
1853             }
1854         }
1855         if (mOverlayManager != null) {
1856             mOverlayManager.release();
1857         }
1858         mMemoryManageables.clear();
1859         if (mMediaSessionWrapper != null) {
1860             mMediaSessionWrapper.release();
1861         }
1862         if (mAudioManagerHelper != null) {
1863             mAudioManagerHelper.release();
1864         }
1865         mHandler.removeCallbacksAndMessages(null);
1866         application.getMainActivityWrapper().onMainActivityDestroyed(this);
1867         if (mSendConfigInfoRecurringRunner != null) {
1868             mSendConfigInfoRecurringRunner.stop();
1869             mSendConfigInfoRecurringRunner = null;
1870         }
1871         if (mChannelStatusRecurringRunner != null) {
1872             mChannelStatusRecurringRunner.stop();
1873             mChannelStatusRecurringRunner = null;
1874         }
1875         if (mTvInputManagerHelper != null) {
1876             mTvInputManagerHelper.clearTvInputLabels();
1877             if (Features.TUNER.isEnabled(this)) {
1878                 mTvInputManagerHelper.removeCallback(mTvInputCallback);
1879             }
1880         }
1881         super.onDestroy();
1882     }
1883 
1884     @Override
1885     public boolean onKeyDown(int keyCode, KeyEvent event) {
1886         if (SystemProperties.LOG_KEYEVENT.getValue()) {
1887             Log.d(TAG, "onKeyDown(" + keyCode + ", " + event + ")");
1888         }
1889         switch (mOverlayManager.onKeyDown(keyCode, event)) {
1890             case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY:
1891                 return super.onKeyDown(keyCode, event);
1892             case KEY_EVENT_HANDLER_RESULT_HANDLED:
1893                 return true;
1894             case KEY_EVENT_HANDLER_RESULT_NOT_HANDLED:
1895                 return false;
1896             case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH:
1897             default:
1898                 // pass through
1899         }
1900         if (mSearchFragment.isVisible()) {
1901             return super.onKeyDown(keyCode, event);
1902         }
1903         if (!mChannelTuner.areAllChannelsLoaded()) {
1904             return false;
1905         }
1906         if (!mChannelTuner.isCurrentChannelPassthrough()) {
1907             switch (keyCode) {
1908                 case KeyEvent.KEYCODE_CHANNEL_UP:
1909                 case KeyEvent.KEYCODE_DPAD_UP:
1910                     if (event.getRepeatCount() == 0
1911                             && mChannelTuner.getBrowsableChannelCount() > 0) {
1912                         // message sending should be done before moving channel, because we use the
1913                         // existence of message to decide if users are switching channel.
1914                         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_UP_PRESSED,
1915                                 System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
1916                         moveToAdjacentChannel(true, false);
1917                         mTracker.sendChannelUp();
1918                     }
1919                     return true;
1920                 case KeyEvent.KEYCODE_CHANNEL_DOWN:
1921                 case KeyEvent.KEYCODE_DPAD_DOWN:
1922                     if (event.getRepeatCount() == 0
1923                             && mChannelTuner.getBrowsableChannelCount() > 0) {
1924                         // message sending should be done before moving channel, because we use the
1925                         // existence of message to decide if users are switching channel.
1926                         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_DOWN_PRESSED,
1927                                 System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
1928                         moveToAdjacentChannel(false, false);
1929                         mTracker.sendChannelDown();
1930                     }
1931                     return true;
1932             }
1933         }
1934         return super.onKeyDown(keyCode, event);
1935     }
1936 
1937     @Override
1938     public boolean onKeyUp(int keyCode, KeyEvent event) {
1939         /*
1940          * The following keyboard keys map to these remote keys or "debug actions"
1941          *  - --------
1942          *  A KEYCODE_MEDIA_AUDIO_TRACK
1943          *  D debug: show debug options
1944          *  E updateChannelBannerAndShowIfNeeded
1945          *  G debug: refresh cloud epg
1946          *  I KEYCODE_TV_INPUT
1947          *  O debug: show display mode option
1948          *  S KEYCODE_CAPTIONS: select subtitle
1949          *  W debug: toggle screen size
1950          *  V KEYCODE_MEDIA_RECORD debug: record the current channel for 30 sec
1951          */
1952         if (SystemProperties.LOG_KEYEVENT.getValue()) {
1953             Log.d(TAG, "onKeyUp(" + keyCode + ", " + event + ")");
1954         }
1955         // If we are in the middle of channel change, finish it before showing overlays.
1956         finishChannelChangeIfNeeded();
1957 
1958         if (event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) {
1959             // Prevent MainActivity from being closed by onVisibleBehindCanceled()
1960             mOtherActivityLaunched = true;
1961             return false;
1962         }
1963         switch (mOverlayManager.onKeyUp(keyCode, event)) {
1964             case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY:
1965                 return super.onKeyUp(keyCode, event);
1966             case KEY_EVENT_HANDLER_RESULT_HANDLED:
1967                 return true;
1968             case KEY_EVENT_HANDLER_RESULT_NOT_HANDLED:
1969                 return false;
1970             case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH:
1971             default:
1972                 // pass through
1973         }
1974         if (mSearchFragment.isVisible()) {
1975             if (keyCode == KeyEvent.KEYCODE_BACK) {
1976                 getFragmentManager().popBackStack();
1977                 return true;
1978             }
1979             return super.onKeyUp(keyCode, event);
1980         }
1981         if (keyCode == KeyEvent.KEYCODE_BACK) {
1982             // When the event is from onUnhandledInputEvent, onBackPressed is not automatically
1983             // called. Therefore, we need to explicitly call onBackPressed().
1984             onBackPressed();
1985             return true;
1986         }
1987 
1988         if (!mChannelTuner.areAllChannelsLoaded()) {
1989             // Now channel map is under loading.
1990         } else if (mChannelTuner.getBrowsableChannelCount() == 0) {
1991             switch (keyCode) {
1992                 case KeyEvent.KEYCODE_CHANNEL_UP:
1993                 case KeyEvent.KEYCODE_DPAD_UP:
1994                 case KeyEvent.KEYCODE_CHANNEL_DOWN:
1995                 case KeyEvent.KEYCODE_DPAD_DOWN:
1996                 case KeyEvent.KEYCODE_NUMPAD_ENTER:
1997                 case KeyEvent.KEYCODE_DPAD_CENTER:
1998                 case KeyEvent.KEYCODE_E:
1999                 case KeyEvent.KEYCODE_MENU:
2000                     showSettingsFragment();
2001                     return true;
2002             }
2003         } else {
2004             if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) {
2005                 mOverlayManager.showKeypadChannelSwitch(keyCode);
2006                 return true;
2007             }
2008             switch (keyCode) {
2009                 case KeyEvent.KEYCODE_DPAD_RIGHT:
2010                     if (!mTvView.isVideoOrAudioAvailable()
2011                             && mTvView.getVideoUnavailableReason()
2012                             == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE) {
2013                         DvrUiHelper.startSchedulesActivityForTuneConflict(this,
2014                                 mChannelTuner.getCurrentChannel());
2015                         return true;
2016                     }
2017                     if (!PermissionUtils.hasModifyParentalControls(this)) {
2018                         return true;
2019                     }
2020                     PinDialogFragment dialog = null;
2021                     if (mTvView.isScreenBlocked()) {
2022                         dialog = PinDialogFragment
2023                                 .create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL);
2024                     } else if (mTvView.isContentBlocked()) {
2025                         dialog = PinDialogFragment
2026                                 .create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM,
2027                                         mTvView.getBlockedContentRating().flattenToString());
2028                     }
2029                     if (dialog != null) {
2030                         mOverlayManager.showDialogFragment(PinDialogFragment.DIALOG_TAG, dialog,
2031                                 false);
2032                     }
2033                     return true;
2034                 case KeyEvent.KEYCODE_WINDOW:
2035                     enterPictureInPictureMode();
2036                     return true;
2037                 case KeyEvent.KEYCODE_ENTER:
2038                 case KeyEvent.KEYCODE_NUMPAD_ENTER:
2039                 case KeyEvent.KEYCODE_E:
2040                 case KeyEvent.KEYCODE_DPAD_CENTER:
2041                 case KeyEvent.KEYCODE_MENU:
2042                     if (event.isCanceled()) {
2043                         // Ignore canceled key.
2044                         // Note that if there's a TIS granted RECEIVE_INPUT_EVENT,
2045                         // fallback keys not blacklisted will have FLAG_CANCELED.
2046                         // See dispatchKeyEvent() for detail.
2047                         return true;
2048                     }
2049                     if (keyCode != KeyEvent.KEYCODE_MENU) {
2050                         mOverlayManager.updateChannelBannerAndShowIfNeeded(
2051                                 TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
2052                     }
2053                     if (keyCode != KeyEvent.KEYCODE_E) {
2054                         mOverlayManager.showMenu(Menu.REASON_NONE);
2055                     }
2056                     return true;
2057                 case KeyEvent.KEYCODE_CHANNEL_UP:
2058                 case KeyEvent.KEYCODE_DPAD_UP:
2059                 case KeyEvent.KEYCODE_CHANNEL_DOWN:
2060                 case KeyEvent.KEYCODE_DPAD_DOWN:
2061                     // Channel change is already done in the head of this method.
2062                     return true;
2063                 case KeyEvent.KEYCODE_S:
2064                     if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
2065                         break;
2066                     }
2067                     // Pass through.
2068                 case KeyEvent.KEYCODE_CAPTIONS: {
2069                     mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment());
2070                     return true;
2071                 }
2072                 case KeyEvent.KEYCODE_A:
2073                     if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
2074                         break;
2075                     }
2076                     // Pass through.
2077                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
2078                     mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment());
2079                     return true;
2080                 }
2081                 case KeyEvent.KEYCODE_INFO: {
2082                     mOverlayManager.showBanner();
2083                     return true;
2084                 }
2085                 case KeyEvent.KEYCODE_MEDIA_RECORD:
2086                 case KeyEvent.KEYCODE_V: {
2087                     Channel currentChannel = getCurrentChannel();
2088                     if (currentChannel != null && mDvrManager != null) {
2089                         boolean isRecording =
2090                                 mDvrManager.getCurrentRecording(currentChannel.getId()) != null;
2091                         if (!isRecording) {
2092                             if (!mDvrManager.isChannelRecordable(currentChannel)) {
2093                                 Toast.makeText(this, R.string.dvr_msg_cannot_record_program,
2094                                         Toast.LENGTH_SHORT).show();
2095                             } else {
2096                                 Program program = mProgramDataManager
2097                                         .getCurrentProgram(currentChannel.getId());
2098                                 DvrUiHelper.checkStorageStatusAndShowErrorMessage(this,
2099                                         currentChannel.getInputId(), new Runnable() {
2100                                             @Override
2101                                             public void run() {
2102                                                 DvrUiHelper.requestRecordingCurrentProgram(
2103                                                         MainActivity.this,
2104                                                         currentChannel, program, false);
2105                                             }
2106                                         });
2107                             }
2108                         } else {
2109                             DvrUiHelper.showStopRecordingDialog(this, currentChannel.getId(),
2110                                     DvrStopRecordingFragment.REASON_USER_STOP,
2111                                     new HalfSizedDialogFragment.OnActionClickListener() {
2112                                         @Override
2113                                         public void onActionClick(long actionId) {
2114                                             if (actionId == DvrStopRecordingFragment.ACTION_STOP) {
2115                                                 ScheduledRecording currentRecording =
2116                                                         mDvrManager.getCurrentRecording(
2117                                                                 currentChannel.getId());
2118                                                 if (currentRecording != null) {
2119                                                     mDvrManager.stopRecording(currentRecording);
2120                                                 }
2121                                             }
2122                                         }
2123                                     });
2124                         }
2125                     }
2126                     return true;
2127                 }
2128             }
2129         }
2130         if (keyCode == KeyEvent.KEYCODE_WINDOW) {
2131             // Consumes the PIP button to prevent entering PIP mode
2132             // in case that TV isn't showing properly (e.g. no browsable channel)
2133             return true;
2134         }
2135         if (SystemProperties.USE_DEBUG_KEYS.getValue() || BuildConfig.ENG) {
2136             switch (keyCode) {
2137                 case KeyEvent.KEYCODE_W:
2138                     mDebugNonFullSizeScreen = !mDebugNonFullSizeScreen;
2139                     if (mDebugNonFullSizeScreen) {
2140                         FrameLayout.LayoutParams params =
2141                                 (FrameLayout.LayoutParams) mTvView.getLayoutParams();
2142                         params.width = 960;
2143                         params.height = 540;
2144                         params.gravity = Gravity.START;
2145                         mTvView.setTvViewLayoutParams(params);
2146                     } else {
2147                         FrameLayout.LayoutParams params =
2148                                 (FrameLayout.LayoutParams) mTvView.getLayoutParams();
2149                         params.width = ViewGroup.LayoutParams.MATCH_PARENT;
2150                         params.height = ViewGroup.LayoutParams.MATCH_PARENT;
2151                         params.gravity = Gravity.CENTER;
2152                         mTvView.setTvViewLayoutParams(params);
2153                     }
2154                     return true;
2155                 case KeyEvent.KEYCODE_CTRL_LEFT:
2156                 case KeyEvent.KEYCODE_CTRL_RIGHT:
2157                     mUseKeycodeBlacklist = !mUseKeycodeBlacklist;
2158                     return true;
2159                 case KeyEvent.KEYCODE_O:
2160                     mOverlayManager.getSideFragmentManager().show(new DisplayModeFragment());
2161                     return true;
2162                 case KeyEvent.KEYCODE_D:
2163                     mOverlayManager.getSideFragmentManager().show(new DeveloperOptionFragment());
2164                     return true;
2165             }
2166         }
2167         return super.onKeyUp(keyCode, event);
2168     }
2169 
2170     @Override
2171     public boolean onKeyLongPress(int keyCode, KeyEvent event) {
2172         if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "onKeyLongPress(" + event);
2173         if (USE_BACK_KEY_LONG_PRESS) {
2174             // Treat the BACK key long press as the normal press since we changed the behavior in
2175             // onBackPressed().
2176             if (keyCode == KeyEvent.KEYCODE_BACK) {
2177                 // It takes long time for TV app to finish, so stop TV first.
2178                 stopAll(false);
2179                 super.onBackPressed();
2180                 return true;
2181             }
2182         }
2183         return false;
2184     }
2185 
2186     @Override
2187     public void onUserInteraction() {
2188         super.onUserInteraction();
2189         if (mOverlayManager != null) {
2190             mOverlayManager.onUserInteraction();
2191         }
2192     }
2193 
2194     @Override
2195     public void enterPictureInPictureMode() {
2196         // We need to hide overlay first, before moving the activity to PIP. If not, UI will
2197         // be shown during PIP stack resizing, because UI and its animation is stuck during
2198         // PIP resizing.
2199         mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
2200         mHandler.post(new Runnable() {
2201             @Override
2202             public void run() {
2203                 MainActivity.super.enterPictureInPictureMode();
2204             }
2205         });
2206     }
2207 
2208     @Override
2209     public void onWindowFocusChanged(boolean hasFocus) {
2210         if (!hasFocus) {
2211             finishChannelChangeIfNeeded();
2212         }
2213     }
2214 
2215     /**
2216      * Returns {@code true} if one of the channel changing keys are pressed and not released yet.
2217      */
2218     public boolean isChannelChangeKeyDownReceived() {
2219         return mHandler.hasMessages(MSG_CHANNEL_UP_PRESSED)
2220                 || mHandler.hasMessages(MSG_CHANNEL_DOWN_PRESSED);
2221     }
2222 
2223     private void finishChannelChangeIfNeeded() {
2224         if (!isChannelChangeKeyDownReceived()) {
2225             return;
2226         }
2227         mHandler.removeMessages(MSG_CHANNEL_UP_PRESSED);
2228         mHandler.removeMessages(MSG_CHANNEL_DOWN_PRESSED);
2229         if (mChannelTuner.getBrowsableChannelCount() > 0) {
2230             if (!mTvView.isPlaying()) {
2231                 // We expect that mTvView is already played. But, it is sometimes not.
2232                 // TODO: we figure out the reason when mTvView is not played.
2233                 Log.w(TAG, "TV view isn't played in finishChannelChangeIfNeeded");
2234             }
2235             tuneToChannel(mChannelTuner.getCurrentChannel());
2236         } else {
2237             showSettingsFragment();
2238         }
2239     }
2240 
2241     private boolean dispatchKeyEventToSession(final KeyEvent event) {
2242         if (SystemProperties.LOG_KEYEVENT.getValue()) {
2243             Log.d(TAG, "dispatchKeyEventToSession(" + event + ")");
2244         }
2245         boolean handled = false;
2246         if (mTvView != null) {
2247             handled = mTvView.dispatchKeyEvent(event);
2248         }
2249         if (isKeyEventBlocked()) {
2250             if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK
2251                     || event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B) && mNeedShowBackKeyGuide) {
2252                 // KeyEvent.KEYCODE_BUTTON_B is also used like the back button.
2253                 Toast.makeText(this, R.string.msg_back_key_guide, Toast.LENGTH_SHORT).show();
2254                 mNeedShowBackKeyGuide = false;
2255             }
2256             return true;
2257         }
2258         return handled;
2259     }
2260 
2261     private boolean isKeyEventBlocked() {
2262         // If the current channel is a passthrough channel, we don't handle the key events in TV
2263         // activity. Instead, the key event will be handled by the passthrough TV input.
2264         return mChannelTuner.isCurrentChannelPassthrough();
2265     }
2266 
2267     private void tuneToLastWatchedChannelForTunerInput() {
2268         if (!mChannelTuner.isCurrentChannelPassthrough()) {
2269             return;
2270         }
2271         stopTv();
2272         startTv(null);
2273     }
2274 
2275     public void tuneToChannel(Channel channel) {
2276         if (channel == null) {
2277             if (mTvView.isPlaying()) {
2278                 mTvView.reset();
2279             }
2280         } else {
2281             if (!mTvView.isPlaying()) {
2282                 startTv(channel.getUri());
2283             } else if (channel.equals(mTvView.getCurrentChannel())) {
2284                 mOverlayManager.updateChannelBannerAndShowIfNeeded(
2285                         TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
2286             } else if (channel == mChannelTuner.getCurrentChannel()) {
2287                 // Channel banner is already updated in moveToAdjacentChannel
2288                 tune(false);
2289             } else if (mChannelTuner.moveToChannel(channel)) {
2290                 // Channel banner would be updated inside of tune.
2291                 tune(true);
2292             } else {
2293                 showSettingsFragment();
2294             }
2295         }
2296     }
2297 
2298     /**
2299      * This method just moves the channel in the channel map and updates the channel banner,
2300      * but doesn't actually tune to the channel.
2301      * The caller of this method should call {@link #tune} in the end.
2302      *
2303      * @param channelUp {@code true} for channel up, and {@code false} for channel down.
2304      * @param fastTuning {@code true} if fast tuning is requested.
2305      */
2306     private void moveToAdjacentChannel(boolean channelUp, boolean fastTuning) {
2307         if (mChannelTuner.moveToAdjacentBrowsableChannel(channelUp)) {
2308             mOverlayManager.updateChannelBannerAndShowIfNeeded(fastTuning ?
2309                     TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST
2310                     : TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
2311         }
2312     }
2313 
2314     /**
2315      * Set the main TV view which holds HDMI-CEC active source based on the sound mode
2316      */
2317     private void restoreMainTvView() {
2318         mTvView.setMain();
2319     }
2320 
2321     @Override
2322     public void onVisibleBehindCanceled() {
2323         stopTv("onVisibleBehindCanceled()", false);
2324         mTracker.sendScreenView("");
2325         mAudioManagerHelper.abandonAudioFocus();
2326         mMediaSessionWrapper.setPlaybackState(false);
2327         mVisibleBehind = false;
2328         if (!mOtherActivityLaunched && Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
2329             // Workaround: in M, onStop is not called, even though it should be called after
2330             // onVisibleBehindCanceled is called. As a workaround, we call finish().
2331             finish();
2332         }
2333         super.onVisibleBehindCanceled();
2334     }
2335 
2336     @Override
2337     public void startActivityForResult(Intent intent, int requestCode) {
2338         mOtherActivityLaunched = true;
2339         if (intent.getCategories() == null
2340                 || !intent.getCategories().contains(Intent.CATEGORY_HOME)) {
2341             // Workaround b/30150267
2342             requestVisibleBehind(false);
2343         }
2344         super.startActivityForResult(intent, requestCode);
2345     }
2346 
2347     public List<TvTrackInfo> getTracks(int type) {
2348         return mTvView.getTracks(type);
2349     }
2350 
2351     public String getSelectedTrack(int type) {
2352         return mTvView.getSelectedTrack(type);
2353     }
2354 
2355     private void selectTrack(int type, TvTrackInfo track, int trackIndex) {
2356         mTvView.selectTrack(type, track == null ? null : track.getId());
2357         if (type == TvTrackInfo.TYPE_AUDIO) {
2358             mTvOptionsManager.onMultiAudioChanged(track == null ? null :
2359                     Utils.getMultiAudioString(this, track, false));
2360         } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
2361             mTvOptionsManager.onClosedCaptionsChanged(track, trackIndex);
2362         }
2363     }
2364 
2365     public void selectAudioTrack(String trackId) {
2366         saveMultiAudioSetting(trackId);
2367         applyMultiAudio();
2368     }
2369 
2370     private void saveMultiAudioSetting(String trackId) {
2371         List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO);
2372         if (tracks != null) {
2373             for (TvTrackInfo track : tracks) {
2374                 if (track.getId().equals(trackId)) {
2375                     TvSettings.setMultiAudioId(this, track.getId());
2376                     TvSettings.setMultiAudioLanguage(this, track.getLanguage());
2377                     TvSettings.setMultiAudioChannelCount(this, track.getAudioChannelCount());
2378                     return;
2379                 }
2380             }
2381         }
2382         TvSettings.setMultiAudioId(this, null);
2383         TvSettings.setMultiAudioLanguage(this, null);
2384         TvSettings.setMultiAudioChannelCount(this, 0);
2385     }
2386 
2387     public void selectSubtitleTrack(int option, String trackId) {
2388         saveClosedCaptionSetting(option, trackId);
2389         applyClosedCaption();
2390     }
2391 
2392     public void selectSubtitleLanguage(int option, String language, String trackId) {
2393         mCaptionSettings.setEnableOption(option);
2394         mCaptionSettings.setLanguage(language);
2395         mCaptionSettings.setTrackId(trackId);
2396         applyClosedCaption();
2397     }
2398 
2399     private void saveClosedCaptionSetting(int option, String trackId) {
2400         mCaptionSettings.setEnableOption(option);
2401         if (option == CaptionSettings.OPTION_ON) {
2402             List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE);
2403             if (tracks != null) {
2404                 for (TvTrackInfo track : tracks) {
2405                     if (track.getId().equals(trackId)) {
2406                         mCaptionSettings.setLanguage(track.getLanguage());
2407                         mCaptionSettings.setTrackId(trackId);
2408                         return;
2409                     }
2410                 }
2411             }
2412         }
2413     }
2414 
2415     private void updateAvailabilityToast() {
2416         if (mTvView.isVideoAvailable()
2417                 || mTvView.getCurrentChannel() != mChannelTuner.getCurrentChannel()) {
2418             return;
2419         }
2420 
2421         switch (mTvView.getVideoUnavailableReason()) {
2422             case TunableTvView.VIDEO_UNAVAILABLE_REASON_NOT_TUNED:
2423             case TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE:
2424             case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
2425             case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
2426             case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:
2427             case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
2428                 return;
2429             case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
2430             default:
2431                 Toast.makeText(this, R.string.msg_channel_unavailable_unknown,
2432                         Toast.LENGTH_SHORT).show();
2433                 break;
2434         }
2435     }
2436 
2437     /**
2438      * Returns {@code true} if some overlay UI will be shown when the activity is resumed.
2439      */
2440     public boolean willShowOverlayUiWhenResume() {
2441         return mInputToSetUp != null || mShowProgramGuide || mShowSelectInputView;
2442     }
2443 
2444     /**
2445      * Returns the current parental control settings.
2446      */
2447     public ParentalControlSettings getParentalControlSettings() {
2448         return mTvInputManagerHelper.getParentalControlSettings();
2449     }
2450 
2451     /**
2452      * Returns a ContentRatingsManager instance.
2453      */
2454     public ContentRatingsManager getContentRatingsManager() {
2455         return mTvInputManagerHelper.getContentRatingsManager();
2456     }
2457 
2458     /**
2459      * Returns the current captioning settings.
2460      */
2461     public CaptionSettings getCaptionSettings() {
2462         return mCaptionSettings;
2463     }
2464 
2465     /**
2466      * Adds the {@link OnActionClickListener}.
2467      */
2468     public void addOnActionClickListener(OnActionClickListener listener) {
2469         mOnActionClickListeners.add(listener);
2470     }
2471 
2472     /**
2473      * Removes the {@link OnActionClickListener}.
2474      */
2475     public void removeOnActionClickListener(OnActionClickListener listener) {
2476         mOnActionClickListeners.remove(listener);
2477     }
2478 
2479     @Override
2480     public boolean onActionClick(String category, int actionId, Bundle params) {
2481         // There should be only one action listener per an action.
2482         for (OnActionClickListener l : mOnActionClickListeners) {
2483             if (l.onActionClick(category, actionId, params)) {
2484                 return true;
2485             }
2486         }
2487         return false;
2488     }
2489 
2490     // Initialize TV app for test. The setup process should be finished before the Live TV app is
2491     // started. We only enable all the channels here.
2492     private void initForTest() {
2493         if (!TvCommonUtils.isRunningInTest()) {
2494             return;
2495         }
2496 
2497         Utils.enableAllChannels(this);
2498     }
2499 
2500     // Lazy initialization
2501     private void lazyInitializeIfNeeded() {
2502         // Already initialized.
2503         if (mLazyInitialized) {
2504             return;
2505         }
2506         mLazyInitialized = true;
2507         // Running initialization.
2508         mHandler.postDelayed(new Runnable() {
2509             @Override
2510             public void run() {
2511                 if (mActivityStarted) {
2512                     initAnimations();
2513                     initSideFragments();
2514                     initMenuItemViews();
2515                 }
2516             }
2517         }, LAZY_INITIALIZATION_DELAY);
2518     }
2519 
2520     private void initAnimations() {
2521         mTvViewUiManager.initAnimatorIfNeeded();
2522         mOverlayManager.initAnimatorIfNeeded();
2523     }
2524 
2525     private void initSideFragments() {
2526         SideFragment.preloadItemViews(this);
2527     }
2528 
2529     private void initMenuItemViews() {
2530         mOverlayManager.getMenu().preloadItemViews();
2531     }
2532 
2533     @Override
2534     public void onTrimMemory(int level) {
2535         super.onTrimMemory(level);
2536         for (MemoryManageable memoryManageable : mMemoryManageables) {
2537             memoryManageable.performTrimMemory(level);
2538         }
2539     }
2540 
2541     private static class MainActivityHandler extends WeakHandler<MainActivity> {
2542         MainActivityHandler(MainActivity mainActivity) {
2543             super(mainActivity);
2544         }
2545 
2546         @Override
2547         protected void handleMessage(Message msg, @NonNull MainActivity mainActivity) {
2548             switch (msg.what) {
2549                 case MSG_CHANNEL_DOWN_PRESSED:
2550                     long startTime = (Long) msg.obj;
2551                     // message re-sending should be done before moving channel, because we use the
2552                     // existence of message to decide if users are switching channel.
2553                     sendMessageDelayed(Message.obtain(msg), getDelay(startTime));
2554                     mainActivity.moveToAdjacentChannel(false, true);
2555                     break;
2556                 case MSG_CHANNEL_UP_PRESSED:
2557                     startTime = (Long) msg.obj;
2558                     // message re-sending should be done before moving channel, because we use the
2559                     // existence of message to decide if users are switching channel.
2560                     sendMessageDelayed(Message.obtain(msg), getDelay(startTime));
2561                     mainActivity.moveToAdjacentChannel(true, true);
2562                     break;
2563             }
2564         }
2565 
2566         private long getDelay(long startTime) {
2567             if (System.currentTimeMillis() - startTime > CHANNEL_CHANGE_NORMAL_SPEED_DURATION_MS) {
2568                 return CHANNEL_CHANGE_DELAY_MS_IN_MAX_SPEED;
2569             }
2570             return CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED;
2571         }
2572     }
2573 
2574     private class MyOnTuneListener implements OnTuneListener {
2575         boolean mUnlockAllowedRatingBeforeShrunken = true;
2576         boolean mWasUnderShrunkenTvView;
2577         Channel mChannel;
2578 
2579         private void onTune(Channel channel, boolean wasUnderShrukenTvView) {
2580             Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.MyOnTuneListener.onTune");
2581             mChannel = channel;
2582             mWasUnderShrunkenTvView = wasUnderShrukenTvView;
2583         }
2584 
2585         @Override
2586         public void onUnexpectedStop(Channel channel) {
2587             stopTv();
2588             startTv(null);
2589         }
2590 
2591         @Override
2592         public void onTuneFailed(Channel channel) {
2593             Log.w(TAG, "onTuneFailed(" + channel + ")");
2594             if (mTvView.isFadedOut()) {
2595                 mTvView.removeFadeEffect();
2596             }
2597             Toast.makeText(MainActivity.this, R.string.msg_channel_unavailable_unknown,
2598                     Toast.LENGTH_SHORT).show();
2599         }
2600 
2601         @Override
2602         public void onStreamInfoChanged(StreamInfo info) {
2603             if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) {
2604                 mTracker.sendChannelTuneTime(info.getCurrentChannel(),
2605                         mTuneDurationTimer.reset());
2606             }
2607             if (info.isVideoOrAudioAvailable() && mChannel == getCurrentChannel()) {
2608                 mOverlayManager.updateChannelBannerAndShowIfNeeded(
2609                         TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO);
2610             }
2611             applyDisplayRefreshRate(info.getVideoFrameRate());
2612             mTvViewUiManager.updateTvAspectRatio();
2613             applyMultiAudio();
2614             applyClosedCaption();
2615             mOverlayManager.getMenu().onStreamInfoChanged();
2616             if (mTvView.isVideoAvailable()) {
2617                 mTvViewUiManager.fadeInTvView();
2618             }
2619             if (!mTvView.isContentBlocked() && !mTvView.isScreenBlocked()) {
2620                 updateAvailabilityToast();
2621             }
2622             mHandler.removeCallbacks(mRestoreMainViewRunnable);
2623             restoreMainTvView();
2624         }
2625 
2626         @Override
2627         public void onChannelRetuned(Uri channel) {
2628             if (channel == null) {
2629                 return;
2630             }
2631             Channel currentChannel =
2632                     mChannelDataManager.getChannel(ContentUris.parseId(channel));
2633             if (currentChannel == null) {
2634                 Log.e(TAG, "onChannelRetuned is called but can't find a channel with the URI "
2635                         + channel);
2636                 return;
2637             }
2638             if (isChannelChangeKeyDownReceived()) {
2639                 // Ignore this message if the user is changing the channel.
2640                 return;
2641             }
2642             mChannelTuner.setCurrentChannel(currentChannel);
2643             mTvView.setCurrentChannel(currentChannel);
2644             mOverlayManager.updateChannelBannerAndShowIfNeeded(
2645                     TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
2646         }
2647 
2648         @Override
2649         public void onContentBlocked() {
2650             Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
2651                     "MainActivity.MyOnTuneListener.onContentBlocked removes timer");
2652             Debug.removeTimer(Debug.TAG_START_UP_TIMER);
2653             mTuneDurationTimer.reset();
2654             TvContentRating rating = mTvView.getBlockedContentRating();
2655             // When tuneTo was called while TV view was shrunken, if the channel id is the same
2656             // with the channel watched before shrunken, we allow the rating which was allowed
2657             // before.
2658             if (mWasUnderShrunkenTvView && mUnlockAllowedRatingBeforeShrunken
2659                     && mChannelBeforeShrunkenTvView.equals(mChannel)
2660                     && rating.equals(mAllowedRatingBeforeShrunken)) {
2661                 mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView();
2662                 mTvView.unblockContent(rating);
2663             }
2664             mOverlayManager.setBlockingContentRating(rating);
2665             mTvViewUiManager.fadeInTvView();
2666             mMediaSessionWrapper.update(true, getCurrentChannel(), getCurrentProgram());
2667         }
2668 
2669         @Override
2670         public void onContentAllowed() {
2671             if (!isUnderShrunkenTvView()) {
2672                 mUnlockAllowedRatingBeforeShrunken = false;
2673             }
2674             mOverlayManager.setBlockingContentRating(null);
2675             mMediaSessionWrapper.update(false, getCurrentChannel(), getCurrentProgram());
2676         }
2677     }
2678 }
2679