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