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