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