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