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