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