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