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