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