TvActivity.java revision f834657141f8d4d750e8da25817f2d28d7beda62
1/* 2 * Copyright (C) 2014 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.DialogFragment; 21import android.app.Fragment; 22import android.app.FragmentManager; 23import android.app.FragmentTransaction; 24import android.content.ContentUris; 25import android.content.Context; 26import android.content.Intent; 27import android.content.SharedPreferences; 28import android.graphics.Point; 29import android.media.AudioManager; 30import android.media.tv.TvInputInfo; 31import android.media.tv.TvInputManager; 32import android.net.Uri; 33import android.os.Bundle; 34import android.os.Handler; 35import android.os.Message; 36import android.preference.PreferenceManager; 37import android.text.TextUtils; 38import android.util.Log; 39import android.view.Display; 40import android.view.GestureDetector; 41import android.view.GestureDetector.SimpleOnGestureListener; 42import android.view.Gravity; 43import android.view.InputEvent; 44import android.view.KeyEvent; 45import android.view.MotionEvent; 46import android.view.View; 47import android.view.ViewGroup; 48import android.view.animation.Animation; 49import android.view.animation.AnimationUtils; 50import android.widget.FrameLayout; 51import android.widget.LinearLayout; 52import android.widget.Toast; 53 54import com.android.tv.data.DisplayMode; 55import com.android.tv.data.Channel; 56import com.android.tv.data.ChannelMap; 57import com.android.tv.data.StreamInfo; 58import com.android.tv.dialog.EditInputDialogFragment; 59import com.android.tv.dialog.RecentlyWatchedDialogFragment; 60import com.android.tv.input.TisTvInput; 61import com.android.tv.input.TvInput; 62import com.android.tv.input.UnifiedTvInput; 63import com.android.tv.notification.NotificationService; 64import com.android.tv.ui.DisplayModeOptionFragment; 65import com.android.tv.ui.BaseSideFragment; 66import com.android.tv.ui.ChannelBannerView; 67import com.android.tv.ui.ClosedCaptionOptionFragment; 68import com.android.tv.ui.EditChannelsFragment; 69import com.android.tv.ui.InputPickerFragment; 70import com.android.tv.ui.MainMenuView; 71import com.android.tv.ui.ChannelNumberView; 72import com.android.tv.ui.PipLocationFragment; 73import com.android.tv.ui.SidePanelContainer; 74import com.android.tv.ui.SimpleGuideFragment; 75import com.android.tv.ui.TunableTvView; 76import com.android.tv.ui.TunableTvView.OnTuneListener; 77import com.android.tv.util.TvInputManagerHelper; 78import com.android.tv.util.TvSettings; 79import com.android.tv.util.Utils; 80 81import java.util.HashSet; 82 83/** 84 * The main activity for demonstrating TV app. 85 */ 86public class TvActivity extends Activity implements AudioManager.OnAudioFocusChangeListener { 87 // STOPSHIP: Turn debugging off 88 private static final boolean DEBUG = true; 89 private static final String TAG = "TvActivity"; 90 91 private static final int MSG_START_TV_RETRY = 1; 92 93 private static final int DURATION_SHOW_CHANNEL_BANNER = 8000; 94 private static final int DURATION_SHOW_CONTROL_GUIDE = 1000; 95 private static final int DURATION_SHOW_MAIN_MENU = 5000; 96 private static final int DURATION_SHOW_SIDE_FRAGMENT = 60000; 97 private static final float AUDIO_MAX_VOLUME = 1.0f; 98 private static final float AUDIO_MIN_VOLUME = 0.0f; 99 private static final float AUDIO_DUCKING_VOLUME = 0.3f; 100 // Wait for 3 seconds 101 private static final int START_TV_MAX_RETRY = 12; 102 private static final int START_TV_RETRY_INTERVAL = 250; 103 104 private static final int SIDE_FRAGMENT_TAG_SHOW = 0; 105 private static final int SIDE_FRAGMENT_TAG_HIDE = 1; 106 private static final int SIDE_FRAGMENT_TAG_RESET = 2; 107 108 // TODO: add more KEYCODEs to the white list. 109 private static final int[] KEYCODE_WHITELIST = { 110 KeyEvent.KEYCODE_0, KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3, 111 KeyEvent.KEYCODE_4, KeyEvent.KEYCODE_5, KeyEvent.KEYCODE_6, KeyEvent.KEYCODE_7, 112 KeyEvent.KEYCODE_8, KeyEvent.KEYCODE_9, KeyEvent.KEYCODE_STAR, KeyEvent.KEYCODE_POUND, 113 KeyEvent.KEYCODE_M, 114 }; 115 // TODO: this value should be able to be toggled in menu. 116 private static final boolean USE_KEYCODE_BLACKLIST = false; 117 private static final int[] KEYCODE_BLACKLIST = { 118 KeyEvent.KEYCODE_MENU, KeyEvent.KEYCODE_CHANNEL_UP, KeyEvent.KEYCODE_CHANNEL_DOWN, 119 KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT 120 }; 121 // STOPSHIP: debug keys are used only for testing. 122 private static final boolean USE_DEBUG_KEYS = true; 123 124 private static final int REQUEST_START_SETUP_ACTIIVTY = 0; 125 private static final int REQUEST_START_SETTINGS_ACTIIVTY = 1; 126 127 private static final String LEANBACK_SET_SHYNESS_BROADCAST = 128 "com.android.mclauncher.action.SET_APP_SHYNESS"; 129 private static final String LEANBACK_SHY_MODE_EXTRA = "shyMode"; 130 131 private static final HashSet<String> AVAILABLE_DIALOG_TAGS = new HashSet<String>(); 132 133 private TvInputManager mTvInputManager; 134 private TunableTvView mTvView; 135 private LinearLayout mControlGuide; 136 private MainMenuView mMainMenuView; 137 private ChannelBannerView mChannelBanner; 138 private ChannelNumberView mChannelNumberView; 139 private SidePanelContainer mSidePanelContainer; 140 private HideRunnable mHideChannelBanner; 141 private HideRunnable mHideControlGuide; 142 private HideRunnable mHideMainMenu; 143 private HideRunnable mHideSideFragment; 144 private int mShortAnimationDuration; 145 private int mDisplayWidth; 146 private GestureDetector mGestureDetector; 147 private ChannelMap mChannelMap; 148 private long mInitChannelId; 149 private String mInitTvInputId; 150 private boolean mChildActivityCanceled; 151 152 private TvInput mTvInputForSetup; 153 private TvInputManagerHelper mTvInputManagerHelper; 154 private AudioManager mAudioManager; 155 private int mAudioFocusStatus; 156 private boolean mTunePendding; 157 private boolean mPipEnabled; 158 private long mPipChannelId; 159 private boolean mDebugNonFullSizeScreen; 160 private boolean mActivityResumed; 161 private boolean mSilenceRequired; 162 private boolean mUseKeycodeBlacklist = USE_KEYCODE_BLACKLIST; 163 private boolean mIsShy = true; 164 165 private boolean mIsClosedCaptionEnabled; 166 private int mDisplayMode; 167 private int mPipLocation; 168 private SharedPreferences mSharedPreferences; 169 170 private SimpleGuideFragment mSimpleGuideFragment; 171 172 static { 173 AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG); 174 AVAILABLE_DIALOG_TAGS.add(EditInputDialogFragment.DIALOG_TAG); 175 } 176 177 // PIP is used for debug/verification of multiple sessions rather than real PIP feature. 178 // When PIP is enabled, the same channel as mTvView is tuned. 179 private TunableTvView mPipView; 180 181 private final Handler mHandler = new Handler() { 182 @Override 183 public void handleMessage(Message msg) { 184 if (msg.what == MSG_START_TV_RETRY) { 185 Object[] arg = (Object[]) msg.obj; 186 TvInput input = (TvInput) arg[0]; 187 long channelId = (Long) arg[1]; 188 int retryCount = msg.arg1; 189 startTvIfAvailableOrRetry(input, channelId, retryCount); 190 } 191 } 192 }; 193 194 @Override 195 protected void onCreate(Bundle savedInstanceState) { 196 super.onCreate(savedInstanceState); 197 198 setContentView(R.layout.activity_tv); 199 mTvView = (TunableTvView) findViewById(R.id.tv_view); 200 mTvView.setOnUnhandledInputEventListener(new TunableTvView.OnUnhandledInputEventListener() { 201 @Override 202 public boolean onUnhandledInputEvent(InputEvent event) { 203 if (event instanceof KeyEvent) { 204 KeyEvent keyEvent = (KeyEvent) event; 205 if (keyEvent.getAction() == KeyEvent.ACTION_UP) { 206 return onKeyUp(keyEvent.getKeyCode(), keyEvent); 207 } 208 } else if (event instanceof MotionEvent) { 209 MotionEvent motionEvent = (MotionEvent) event; 210 if (motionEvent.isTouchEvent()) { 211 return onTouchEvent(motionEvent); 212 } 213 } 214 return false; 215 } 216 }); 217 mPipView = (TunableTvView) findViewById(R.id.pip_view); 218 mPipView.setPip(true); 219 220 mControlGuide = (LinearLayout) findViewById(R.id.control_guide); 221 mChannelBanner = (ChannelBannerView) findViewById(R.id.channel_banner); 222 mMainMenuView = (MainMenuView) findViewById(R.id.main_menu); 223 mSidePanelContainer = (SidePanelContainer) findViewById(R.id.right_panel); 224 mMainMenuView.setTvActivity(this); 225 mChannelNumberView = (ChannelNumberView) findViewById(R.id.channel_number_view); 226 mChannelNumberView.setTvActivity(this); 227 228 // Initially hide the channel banner and the control guide. 229 mChannelBanner.setVisibility(View.GONE); 230 mMainMenuView.setVisibility(View.GONE); 231 mControlGuide.setVisibility(View.GONE); 232 mSidePanelContainer.setVisibility(View.GONE); 233 mSidePanelContainer.setTag(SIDE_FRAGMENT_TAG_RESET); 234 235 mHideControlGuide = new HideRunnable(mControlGuide, DURATION_SHOW_CONTROL_GUIDE); 236 mHideChannelBanner = new HideRunnable(mChannelBanner, DURATION_SHOW_CHANNEL_BANNER); 237 mHideMainMenu = new HideRunnable(mMainMenuView, DURATION_SHOW_MAIN_MENU, 238 new Runnable() { 239 @Override 240 public void run() { 241 if (mPipEnabled) { 242 mPipView.setVisibility(View.INVISIBLE); 243 } 244 } 245 }, 246 new Runnable() { 247 @Override 248 public void run() { 249 if (mPipEnabled && mActivityResumed) { 250 mPipView.setVisibility(View.VISIBLE); 251 } 252 } 253 }); 254 mHideSideFragment = new HideRunnable(mSidePanelContainer, DURATION_SHOW_SIDE_FRAGMENT, null, 255 new Runnable() { 256 @Override 257 public void run() { 258 resetSideFragment(); 259 } 260 }); 261 262 mShortAnimationDuration = getResources().getInteger( 263 android.R.integer.config_shortAnimTime); 264 265 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 266 mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; 267 Display display = getWindowManager().getDefaultDisplay(); 268 Point size = new Point(); 269 display.getSize(size); 270 mDisplayWidth = size.x; 271 272 mGestureDetector = new GestureDetector(this, new SimpleOnGestureListener() { 273 static final float CONTROL_MARGIN = 0.2f; 274 final float mLeftMargin = mDisplayWidth * CONTROL_MARGIN; 275 final float mRightMargin = mDisplayWidth * (1 - CONTROL_MARGIN); 276 277 @Override 278 public boolean onDown(MotionEvent event) { 279 if (DEBUG) Log.d(TAG, "onDown: " + event.toString()); 280 if (mChannelMap == null) { 281 return false; 282 } 283 284 mHideControlGuide.showAndHide(); 285 286 if (event.getX() <= mLeftMargin) { 287 channelDown(); 288 return true; 289 } else if (event.getX() >= mRightMargin) { 290 channelUp(); 291 return true; 292 } 293 return false; 294 } 295 296 @Override 297 public boolean onSingleTapUp(MotionEvent event) { 298 if (mChannelMap == null) { 299 showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN); 300 return true; 301 } 302 303 if (event.getX() > mLeftMargin && event.getX() < mRightMargin) { 304 displayMainMenu(true); 305 return true; 306 } 307 return false; 308 } 309 }); 310 311 mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); 312 mTvInputManagerHelper = new TvInputManagerHelper(mTvInputManager); 313 mTvInputManagerHelper.start(); 314 315 mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); 316 restoreClosedCaptionEnabled(); 317 restoreDisplayMode(); 318 restorePipLocation(); 319 onNewIntent(getIntent()); 320 } 321 322 @Override 323 protected void onNewIntent(Intent intent) { 324 // Handle the passed key press, if any. Note that only the key codes that are currently 325 // handled in the TV app will be handled via Intent. 326 // TODO: Consider defining a separate intent filter as passing data of mime type 327 // vnd.android.cursor.item/channel isn't really necessary here. 328 int keyCode = intent.getIntExtra(Utils.EXTRA_KEYCODE, KeyEvent.KEYCODE_UNKNOWN); 329 if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { 330 if (DEBUG) Log.d(TAG, "Got an intent with keycode: " + keyCode); 331 KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, keyCode); 332 onKeyUp(keyCode, event); 333 return; 334 } 335 336 if (Intent.ACTION_VIEW.equals(intent.getAction())) { 337 // In case the channel is given explicitly, use it. 338 mInitChannelId = ContentUris.parseId(intent.getData()); 339 } else { 340 mInitChannelId = Channel.INVALID_ID; 341 } 342 } 343 344 @Override 345 protected void onStart() { 346 super.onStart(); 347 } 348 349 @Override 350 protected void onResume() { 351 super.onResume(); 352 mActivityResumed = true; 353 mSilenceRequired = false; 354 mTvInputManagerHelper.update(); 355 if (mTvInputManagerHelper.getTvInputSize() == 0) { 356 Toast.makeText(this, R.string.no_input_device_found, Toast.LENGTH_SHORT).show(); 357 // TODO: Direct the user to a Play Store landing page for TvInputService apps. 358 return; 359 } 360 boolean tvStarted = false; 361 if (mInitTvInputId != null) { 362 TvInputInfo inputInfo = mTvInputManagerHelper.getTvInputInfo(mInitTvInputId); 363 if (inputInfo != null) { 364 startTvIfAvailableOrRetry(new TisTvInput(mTvInputManagerHelper, inputInfo, this), 365 Channel.INVALID_ID, 0); 366 tvStarted = true; 367 } 368 } 369 if (!tvStarted) { 370 startTv(mInitChannelId); 371 } 372 mInitChannelId = Channel.INVALID_ID; 373 mInitTvInputId = null; 374 if (mPipEnabled) { 375 if (!mPipView.isPlaying()) { 376 startPip(); 377 } else if (!mPipView.isShown()) { 378 mPipView.setVisibility(View.VISIBLE); 379 } 380 } 381 } 382 383 @Override 384 protected void onPause() { 385 hideOverlays(true, true, true); 386 if (mPipEnabled) { 387 mPipView.setVisibility(View.INVISIBLE); 388 } 389 mActivityResumed = false; 390 super.onPause(); 391 } 392 393 private void startTv(long channelId) { 394 if (mTvView.isPlaying()) { 395 // TV has already started. 396 if (channelId == Channel.INVALID_ID) { 397 // Simply adjust the volume without tune. 398 setVolumeByAudioFocusStatus(); 399 return; 400 } 401 Uri channelUri = mChannelMap.getCurrentChannelUri(); 402 if (channelUri != null && ContentUris.parseId(channelUri) == channelId) { 403 // The requested channel is already tuned. 404 setVolumeByAudioFocusStatus(); 405 return; 406 } 407 stopTv(); 408 } 409 410 if (channelId == Channel.INVALID_ID) { 411 // If any initial channel id is not given, remember the last channel the user watched. 412 channelId = Utils.getLastWatchedChannelId(this); 413 } 414 if (channelId == Channel.INVALID_ID) { 415 // If failed to pick a channel, try a different input. 416 showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN); 417 return; 418 } 419 String inputId = Utils.getInputIdForChannel(this, channelId); 420 if (TextUtils.isEmpty(inputId)) { 421 // If the channel is invalid, try to use the last selected physical tv input. 422 inputId = Utils.getLastSelectedPhysInputId(this); 423 if (TextUtils.isEmpty(inputId)) { 424 // If failed to determine the input for that channel, try a different input. 425 showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN); 426 return; 427 } 428 } 429 TvInputInfo inputInfo = mTvInputManagerHelper.getTvInputInfo(inputId); 430 if (inputInfo == null) { 431 // TODO: if the last selected TV input is uninstalled, getLastWatchedChannelId 432 // should return Channel.INVALID_ID. 433 Log.w(TAG, "Input (id=" + inputId + ") doesn't exist"); 434 showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN); 435 return; 436 } 437 String lastSelectedInputId = Utils.getLastSelectedInputId(this); 438 TvInput input; 439 if (UnifiedTvInput.ID.equals(lastSelectedInputId)) { 440 input = new UnifiedTvInput(mTvInputManagerHelper, this); 441 } else { 442 input = new TisTvInput(mTvInputManagerHelper, inputInfo, this); 443 } 444 startTvIfAvailableOrRetry(input, channelId, 0); 445 } 446 447 private void startTvIfAvailableOrRetry(TvInput input, long channelId, int retryCount) { 448 if (!input.isAvailable()) { 449 if (retryCount >= START_TV_MAX_RETRY) { 450 showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN); 451 return; 452 } 453 if (DEBUG) Log.d(TAG, "Retry start TV (retryCount=" + retryCount + ")"); 454 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TV_RETRY, 455 retryCount + 1, 0, new Object[]{input, channelId}), 456 START_TV_RETRY_INTERVAL); 457 return; 458 } 459 startTv(input, channelId); 460 } 461 462 @Override 463 protected void onStop() { 464 if (DEBUG) Log.d(TAG, "onStop()"); 465 hideOverlays(true, true, true, false); 466 mHandler.removeMessages(MSG_START_TV_RETRY); 467 stopTv(); 468 stopPip(); 469 super.onStop(); 470 } 471 472 public void onInputPicked(TvInput input) { 473 if (input.equals(getSelectedTvInput())) { 474 // Nothing has changed thus nothing to do. 475 return; 476 } 477 if (!input.hasChannel(false)) { 478 mTvInputForSetup = null; 479 if (!startSetupActivity(input)) { 480 String message = String.format( 481 getString(R.string.empty_channel_tvinput_and_no_setup_activity), 482 input.getDisplayName()); 483 Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); 484 showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN); 485 } 486 return; 487 } 488 489 stopTv(); 490 startTvWithLastWatchedChannel(input); 491 } 492 493 public TvInputManagerHelper getTvInputManagerHelper() { 494 return mTvInputManagerHelper; 495 } 496 497 public TvInput getSelectedTvInput() { 498 return mChannelMap == null ? null : mChannelMap.getTvInput(); 499 } 500 501 public void showEditChannelsFragment(int initiator) { 502 showSideFragment(new EditChannelsFragment(mChannelMap.getChannelList(false)), initiator); 503 } 504 505 public boolean startSetupActivity() { 506 if (getSelectedTvInput() == null) { 507 return false; 508 } 509 return startSetupActivity(getSelectedTvInput()); 510 } 511 512 public boolean startSetupActivity(TvInput input) { 513 Intent intent = input.getIntentForSetupActivity(); 514 if (intent == null) { 515 return false; 516 } 517 startActivityForResult(intent, REQUEST_START_SETUP_ACTIIVTY); 518 mTvInputForSetup = input; 519 mInitTvInputId = null; 520 stopTv(); 521 return true; 522 } 523 524 public boolean startSettingsActivity() { 525 TvInput input = getSelectedTvInput(); 526 if (input == null) { 527 Log.w(TAG, "There is no selected TV input during startSettingsActivity"); 528 return false; 529 } 530 Intent intent = input.getIntentForSettingsActivity(); 531 if (intent == null) { 532 return false; 533 } 534 startActivityForResult(intent, REQUEST_START_SETTINGS_ACTIIVTY); 535 return true; 536 } 537 538 public void showSimpleGuide(int initiator) { 539 mSimpleGuideFragment = new SimpleGuideFragment(this, mChannelMap); 540 showSideFragment(mSimpleGuideFragment, initiator); 541 } 542 543 public void showInputPicker(int initiator) { 544 showSideFragment(new InputPickerFragment(), initiator); 545 } 546 547 public void showDisplayModeOption(int initiator) { 548 showSideFragment(new DisplayModeOptionFragment(), initiator); 549 } 550 551 public void showClosedCaptionOption(int initiator) { 552 showSideFragment(new ClosedCaptionOptionFragment(), initiator); 553 } 554 555 public void showPipLocationOption(int initiator) { 556 showSideFragment(new PipLocationFragment(), initiator); 557 } 558 559 public void showSideFragment(Fragment f, int initiator) { 560 mSidePanelContainer.setTag(SIDE_FRAGMENT_TAG_SHOW); 561 mSidePanelContainer.setKeyDispatchable(true); 562 563 Bundle bundle = new Bundle(); 564 bundle.putInt(BaseSideFragment.KEY_INITIATOR, initiator); 565 f.setArguments(bundle); 566 FragmentTransaction ft = getFragmentManager().beginTransaction(); 567 ft.add(R.id.right_panel, f); 568 ft.addToBackStack(null); 569 ft.commit(); 570 571 mHideSideFragment.showAndHide(); 572 } 573 574 public void popFragmentBackStack() { 575 if (getFragmentManager().getBackStackEntryCount() > 1) { 576 getFragmentManager().popBackStack(); 577 } else if (getFragmentManager().getBackStackEntryCount() == 1 578 && mSidePanelContainer.getTag() != SIDE_FRAGMENT_TAG_RESET) { 579 if (mSidePanelContainer.getTag() == SIDE_FRAGMENT_TAG_SHOW) { 580 mSidePanelContainer.setKeyDispatchable(false); 581 mSidePanelContainer.setTag(SIDE_FRAGMENT_TAG_HIDE); 582 mHideSideFragment.hideImmediately(true); 583 } else { 584 // It is during fade-out animation. 585 } 586 } else { 587 getFragmentManager().popBackStack(); 588 } 589 } 590 591 public void onSideFragmentCanceled(int initiator) { 592 if (mSidePanelContainer.getTag() == SIDE_FRAGMENT_TAG_RESET) { 593 return; 594 } 595 if (initiator == BaseSideFragment.INITIATOR_MENU) { 596 displayMainMenu(false); 597 } 598 } 599 600 private void resetSideFragment() { 601 while (true) { 602 if (!getFragmentManager().popBackStackImmediate()) { 603 break; 604 } 605 } 606 mSidePanelContainer.setTag(SIDE_FRAGMENT_TAG_RESET); 607 } 608 609 @Override 610 public void onActivityResult(int requestCode, int resultCode, Intent data) { 611 if (resultCode == Activity.RESULT_OK) { 612 switch (requestCode) { 613 case REQUEST_START_SETUP_ACTIIVTY: 614 if (mTvInputForSetup != null) { 615 mInitTvInputId = mTvInputForSetup.getId(); 616 } 617 break; 618 619 default: 620 //TODO: Handle failure of setup. 621 } 622 } else { 623 mChildActivityCanceled = true; 624 } 625 mTvInputForSetup = null; 626 } 627 628 @Override 629 public boolean dispatchKeyEvent(KeyEvent event) { 630 if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")"); 631 int eventKeyCode = event.getKeyCode(); 632 if (mUseKeycodeBlacklist) { 633 for (int keycode : KEYCODE_BLACKLIST) { 634 if (keycode == eventKeyCode) { 635 return super.dispatchKeyEvent(event); 636 } 637 } 638 return dispatchKeyEventToSession(event); 639 } else { 640 for (int keycode : KEYCODE_WHITELIST) { 641 if (keycode == eventKeyCode) { 642 return dispatchKeyEventToSession(event); 643 } 644 } 645 return super.dispatchKeyEvent(event); 646 } 647 } 648 649 @Override 650 public void onAudioFocusChange(int focusChange) { 651 mAudioFocusStatus = focusChange; 652 setVolumeByAudioFocusStatus(); 653 } 654 655 private void setVolumeByAudioFocusStatus() { 656 if (mTvView.isPlaying()) { 657 switch (mAudioFocusStatus) { 658 case AudioManager.AUDIOFOCUS_GAIN: 659 if (!mSilenceRequired) { 660 mTvView.setStreamVolume(AUDIO_MAX_VOLUME); 661 if (isShyModeSet()) { 662 setShynessMode(false); 663 } 664 } else { 665 mTvView.setStreamVolume(AUDIO_MIN_VOLUME); 666 } 667 break; 668 case AudioManager.AUDIOFOCUS_LOSS: 669 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 670 mTvView.setStreamVolume(AUDIO_MIN_VOLUME); 671 if (!mActivityResumed) { 672 mSilenceRequired = true; 673 } 674 break; 675 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 676 if (!mSilenceRequired) { 677 mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME); 678 } else { 679 mTvView.setStreamVolume(AUDIO_MIN_VOLUME); 680 } 681 break; 682 } 683 } 684 // When the activity loses the audio focus, set the Shy mode regardless of the play status. 685 if (mAudioFocusStatus == AudioManager.AUDIOFOCUS_LOSS || 686 mAudioFocusStatus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { 687 if (!isShyModeSet()) { 688 setShynessMode(true); 689 } 690 } 691 } 692 693 private void startTvWithLastWatchedChannel(TvInput input) { 694 long channelId = Utils.getLastWatchedChannelId(TvActivity.this, input.getId()); 695 startTv(input, channelId); 696 } 697 698 private void startTv(TvInput input, long channelId) { 699 if (mChannelMap != null) { 700 // TODO: when this case occurs, we should remove the case. 701 Log.w(TAG, "The previous variables are not released in startTv"); 702 stopTv(); 703 } 704 705 mMainMenuView.setChannelMap(null); 706 mChannelNumberView.setChannels(null); 707 int result = mAudioManager.requestAudioFocus(TvActivity.this, 708 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); 709 mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ? 710 AudioManager.AUDIOFOCUS_GAIN : AudioManager.AUDIOFOCUS_LOSS; 711 712 // Prepare a new channel map for the current input. 713 mChannelMap = input.buildChannelMap(this, channelId, mOnChannelsLoadFinished); 714 mTvView.start(mTvInputManagerHelper); 715 setVolumeByAudioFocusStatus(); 716 tune(); 717 } 718 719 private void stopTv() { 720 if (mTvView.isPlaying()) { 721 mTvView.stop(); 722 mAudioManager.abandonAudioFocus(this); 723 } 724 if (mChannelMap != null) { 725 mMainMenuView.setChannelMap(null); 726 mChannelNumberView.setChannels(null); 727 mChannelMap.close(); 728 mChannelMap = null; 729 } 730 mTunePendding = false; 731 732 if (!isShyModeSet()) { 733 setShynessMode(true); 734 } 735 } 736 737 private boolean isPlaying() { 738 return mTvView.isPlaying() && mTvView.getCurrentChannelId() != Channel.INVALID_ID; 739 } 740 741 private void startPip() { 742 if (mPipChannelId == Channel.INVALID_ID) { 743 Log.w(TAG, "PIP channel id is an invalid id."); 744 return; 745 } 746 if (DEBUG) Log.d(TAG, "startPip()"); 747 mPipView.start(mTvInputManagerHelper); 748 boolean success = mPipView.tuneTo(mPipChannelId, new OnTuneListener() { 749 @Override 750 public void onUnexpectedStop(long channelId) { 751 Log.w(TAG, "The PIP is Unexpectedly stopped"); 752 enablePipView(false); 753 } 754 755 @Override 756 public void onTuned(boolean success, long channelId) { 757 if (!success) { 758 Log.w(TAG, "Fail to start the PIP during channel tunning"); 759 enablePipView(false); 760 } else { 761 mPipView.setVisibility(View.VISIBLE); 762 } 763 } 764 765 @Override 766 public void onStreamInfoChanged(StreamInfo info) { 767 // Do nothing. 768 } 769 }); 770 if (!success) { 771 Log.w(TAG, "Fail to start the PIP"); 772 return; 773 } 774 mPipView.setStreamVolume(AUDIO_MIN_VOLUME); 775 } 776 777 private void stopPip() { 778 if (DEBUG) Log.d(TAG, "stopPip"); 779 if (mPipView.isPlaying()) { 780 mPipView.setVisibility(View.INVISIBLE); 781 mPipView.stop(); 782 } 783 } 784 785 private final Runnable mOnChannelsLoadFinished = new Runnable() { 786 @Override 787 public void run() { 788 if (mTunePendding) { 789 tune(); 790 } 791 792 mChannelNumberView.setChannels( 793 (mChannelMap == null) ? null : mChannelMap.getChannelList(false)); 794 mMainMenuView.setChannelMap(mChannelMap); 795 } 796 }; 797 798 private void tune() { 799 if (DEBUG) Log.d(TAG, "tune()"); 800 // Prerequisites to be able to tune. 801 if (mChannelMap == null || !mChannelMap.isLoadFinished()) { 802 if (DEBUG) Log.d(TAG, "Channel map not ready"); 803 mTunePendding = true; 804 return; 805 } 806 mTunePendding = false; 807 long channelId = mChannelMap.getCurrentChannelId(); 808 final String inputId = mChannelMap.getTvInput().getId(); 809 if (channelId == Channel.INVALID_ID) { 810 stopTv(); 811 Toast.makeText(this, R.string.input_is_not_available, Toast.LENGTH_SHORT).show(); 812 return; 813 } 814 815 mTvView.tuneTo(channelId, new OnTuneListener() { 816 @Override 817 public void onUnexpectedStop(long channelId) { 818 stopTv(); 819 startTv(Channel.INVALID_ID); 820 } 821 822 @Override 823 public void onTuned(boolean success, long channelId) { 824 if (!success) { 825 Log.w(TAG, "Failed to tune to channel " + channelId); 826 // TODO: show something to user about this error. 827 } else { 828 Utils.setLastWatchedChannelId(TvActivity.this, inputId, 829 mTvView.getCurrentTvInputInfo().getId(), channelId); 830 } 831 } 832 833 @Override 834 public void onStreamInfoChanged(StreamInfo info) { 835 updateChannelBanner(false); 836 applyDisplayMode(info.getVideoWidth(), info.getVideoHeight()); 837 } 838 }); 839 updateChannelBanner(true); 840 if (mChildActivityCanceled) { 841 displayMainMenu(false); 842 mChildActivityCanceled = false; 843 } 844 if (isShyModeSet()) { 845 setShynessMode(false); 846 // TODO: Set the shy mode to true when tune() fails. 847 } 848 } 849 850 public void hideOverlays(boolean hideMainMenu, boolean hideChannelBanner, 851 boolean hideSidePanel) { 852 hideOverlays(hideMainMenu, hideChannelBanner, hideSidePanel, true); 853 } 854 855 public void hideOverlays(boolean hideMainMenu, boolean hideChannelBanner, 856 boolean hideSidePanel, boolean withAnimation) { 857 if (hideMainMenu) { 858 mHideMainMenu.hideImmediately(withAnimation); 859 } 860 if (hideChannelBanner) { 861 mHideChannelBanner.hideImmediately(withAnimation); 862 } 863 if (hideSidePanel) { 864 if (mSidePanelContainer.getTag() != SIDE_FRAGMENT_TAG_SHOW) { 865 return; 866 } 867 mSidePanelContainer.setTag(SIDE_FRAGMENT_TAG_HIDE); 868 mHideSideFragment.hideImmediately(withAnimation); 869 } 870 } 871 872 private void applyDisplayMode(int videoWidth, int videoHeight) { 873 int decorViewWidth = getWindow().getDecorView().getWidth(); 874 int decorViewHeight = getWindow().getDecorView().getHeight(); 875 ViewGroup.LayoutParams layoutParams = mTvView.getLayoutParams(); 876 int displayMode = mDisplayMode; 877 double decorViewRatio = 0; 878 double videoRatio = 0; 879 if (decorViewWidth <= 0 || decorViewHeight <= 0 || videoWidth <= 0 || videoHeight <=0) { 880 displayMode = DisplayMode.MODE_FULL; 881 Log.w(TAG, "Some resolution info is missing during applyDisplayMode. (" 882 + "decorViewWidth=" + decorViewWidth + ", decorViewHeight=" + decorViewHeight 883 + ", width=" + videoWidth + ", height=" + videoHeight + ")"); 884 } else { 885 decorViewRatio = (double) decorViewWidth / decorViewHeight; 886 videoRatio = (double) videoWidth / videoHeight; 887 } 888 if (displayMode == DisplayMode.MODE_FULL) { 889 layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 890 layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 891 } else if (displayMode == DisplayMode.MODE_ZOOM) { 892 if (videoRatio < decorViewRatio) { 893 // Y axis will be clipped. 894 layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 895 layoutParams.height = (int) (decorViewWidth / videoRatio); 896 } else { 897 // X axis will be clipped. 898 layoutParams.width = (int) (decorViewHeight * videoRatio); 899 layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 900 } 901 } else { 902 // MODE_NORMAL (default value) 903 if (videoRatio < decorViewRatio) { 904 // X axis has black area. 905 layoutParams.width = (int) (decorViewHeight * videoRatio); 906 layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 907 } else { 908 // Y axis has black area. 909 layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 910 layoutParams.height = (int) (decorViewWidth / videoRatio); 911 } 912 } 913 mTvView.setLayoutParams(layoutParams); 914 } 915 916 private void updateChannelBanner(final boolean showBanner) { 917 runOnUiThread(new Runnable() { 918 @Override 919 public void run() { 920 if (mChannelMap == null || !mChannelMap.isLoadFinished()) { 921 return; 922 } 923 924 mChannelBanner.updateViews(mChannelMap, mTvView); 925 if (showBanner) { 926 mHideChannelBanner.showAndHide(); 927 } 928 } 929 }); 930 } 931 932 private void displayMainMenu(final boolean resetSelectedItemPosition) { 933 runOnUiThread(new Runnable() { 934 @Override 935 public void run() { 936 if (mChannelMap == null || !mChannelMap.isLoadFinished()) { 937 return; 938 } 939 940 if (!mMainMenuView.isShown() && resetSelectedItemPosition) { 941 mMainMenuView.resetSelectedItemPosition(); 942 } 943 mHideMainMenu.showAndHide(); 944 } 945 }); 946 } 947 948 public void showRecentlyWatchedDialog() { 949 showDialogFragment(RecentlyWatchedDialogFragment.DIALOG_TAG, 950 new RecentlyWatchedDialogFragment()); 951 } 952 953 @Override 954 protected void onSaveInstanceState(Bundle outState) { 955 // Do not save instance state because restoring instance state when TV app died 956 // unexpectedly can cause some problems like initializing fragments duplicately and 957 // accessing resource before it is initialzed. 958 } 959 960 @Override 961 protected void onDestroy() { 962 if (DEBUG) Log.d(TAG, "onDestroy()"); 963 mTvInputManagerHelper.stop(); 964 super.onDestroy(); 965 } 966 967 @Override 968 public boolean onKeyUp(int keyCode, KeyEvent event) { 969 if (getFragmentManager().getBackStackEntryCount() > 0) { 970 if (keyCode == KeyEvent.KEYCODE_BACK) { 971 popFragmentBackStack(); 972 return true; 973 } 974 return super.onKeyUp(keyCode, event); 975 } 976 if (mMainMenuView.isShown() || mChannelBanner.isShown()) { 977 if (keyCode == KeyEvent.KEYCODE_BACK) { 978 hideOverlays(true, true, false); 979 return true; 980 } 981 if (mMainMenuView.isShown()) { 982 return super.onKeyUp(keyCode, event); 983 } 984 } 985 if (mChannelNumberView.isShown()) { 986 if (keyCode == KeyEvent.KEYCODE_BACK) { 987 mChannelNumberView.hide(); 988 return true; 989 } 990 return mChannelNumberView.onKeyUp(keyCode, event); 991 } 992 if (mHandler.hasMessages(MSG_START_TV_RETRY)) { 993 // Ignore key events during startTv retry. 994 return true; 995 } 996 if (mChannelMap == null) { 997 switch (keyCode) { 998 case KeyEvent.KEYCODE_H: 999 showRecentlyWatchedDialog(); 1000 return true; 1001 case KeyEvent.KEYCODE_TV_INPUT: 1002 case KeyEvent.KEYCODE_I: 1003 case KeyEvent.KEYCODE_CHANNEL_UP: 1004 case KeyEvent.KEYCODE_DPAD_UP: 1005 case KeyEvent.KEYCODE_CHANNEL_DOWN: 1006 case KeyEvent.KEYCODE_DPAD_DOWN: 1007 case KeyEvent.KEYCODE_NUMPAD_ENTER: 1008 case KeyEvent.KEYCODE_DPAD_CENTER: 1009 case KeyEvent.KEYCODE_E: 1010 case KeyEvent.KEYCODE_MENU: 1011 showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN); 1012 return true; 1013 } 1014 } else { 1015 if (ChannelNumberView.isChannelNumberKey(keyCode) 1016 && keyCode != KeyEvent.KEYCODE_MINUS) { 1017 mChannelNumberView.show(); 1018 mHideChannelBanner.hideImmediately(true); 1019 return mChannelNumberView.onKeyUp(keyCode, event); 1020 } 1021 switch (keyCode) { 1022 case KeyEvent.KEYCODE_H: 1023 showRecentlyWatchedDialog(); 1024 return true; 1025 1026 case KeyEvent.KEYCODE_TV_INPUT: 1027 case KeyEvent.KEYCODE_I: 1028 showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN); 1029 return true; 1030 1031 case KeyEvent.KEYCODE_CHANNEL_UP: 1032 case KeyEvent.KEYCODE_DPAD_UP: 1033 channelUp(); 1034 return true; 1035 1036 case KeyEvent.KEYCODE_CHANNEL_DOWN: 1037 case KeyEvent.KEYCODE_DPAD_DOWN: 1038 channelDown(); 1039 return true; 1040 1041 case KeyEvent.KEYCODE_DPAD_LEFT: 1042 case KeyEvent.KEYCODE_DPAD_RIGHT: 1043 displayMainMenu(true); 1044 return true; 1045 1046 case KeyEvent.KEYCODE_ENTER: 1047 case KeyEvent.KEYCODE_NUMPAD_ENTER: 1048 case KeyEvent.KEYCODE_E: 1049 case KeyEvent.KEYCODE_DPAD_CENTER: 1050 case KeyEvent.KEYCODE_MENU: 1051 if (event.isCanceled()) { 1052 return true; 1053 } 1054 if (keyCode != KeyEvent.KEYCODE_MENU) { 1055 updateChannelBanner(true); 1056 } 1057 if (keyCode != KeyEvent.KEYCODE_E) { 1058 displayMainMenu(true); 1059 } 1060 return true; 1061 } 1062 } 1063 if (USE_DEBUG_KEYS) { 1064 switch (keyCode) { 1065 case KeyEvent.KEYCODE_W: { 1066 mDebugNonFullSizeScreen = !mDebugNonFullSizeScreen; 1067 if (mDebugNonFullSizeScreen) { 1068 mTvView.layout(100, 100, 400, 300); 1069 } else { 1070 ViewGroup.LayoutParams params = mTvView.getLayoutParams(); 1071 params.width = ViewGroup.LayoutParams.MATCH_PARENT; 1072 params.height = ViewGroup.LayoutParams.MATCH_PARENT; 1073 mTvView.setLayoutParams(params); 1074 } 1075 return true; 1076 } 1077 case KeyEvent.KEYCODE_P: { 1078 togglePipView(); 1079 return true; 1080 } 1081 case KeyEvent.KEYCODE_CTRL_LEFT: 1082 case KeyEvent.KEYCODE_CTRL_RIGHT: { 1083 mUseKeycodeBlacklist = !mUseKeycodeBlacklist; 1084 return true; 1085 } 1086 case KeyEvent.KEYCODE_O: { 1087 showDisplayModeOption(BaseSideFragment.INITIATOR_SHORTCUT_KEY); 1088 return true; 1089 } 1090 } 1091 } 1092 return super.onKeyUp(keyCode, event); 1093 } 1094 1095 @Override 1096 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 1097 if (DEBUG) Log.d(TAG, "onKeyLongPress(" + event); 1098 // Treat the BACK key long press as the normal press since we changed the behavior in 1099 // onBackPressed(). 1100 if (keyCode == KeyEvent.KEYCODE_BACK) { 1101 super.onBackPressed(); 1102 return true; 1103 } 1104 return false; 1105 } 1106 1107 @Override 1108 public void onBackPressed() { 1109 if (getFragmentManager().getBackStackEntryCount() <= 0 && isPlaying()) { 1110 // TODO: show the following toast message in the future. 1111// Toast.makeText(getApplicationContext(), getResources().getString( 1112// R.string.long_press_back), Toast.LENGTH_SHORT).show(); 1113 1114 // If back key would exit TV app, 1115 // show McLauncher instead so we can get benefit of McLauncher's shyMode. 1116 Intent startMain = new Intent(Intent.ACTION_MAIN); 1117 startMain.addCategory(Intent.CATEGORY_HOME); 1118 startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1119 startActivity(startMain); 1120 } else { 1121 super.onBackPressed(); 1122 } 1123 } 1124 1125 @Override 1126 public void onUserInteraction() { 1127 super.onUserInteraction(); 1128 if (mHideMainMenu.hasFocus() && mSidePanelContainer.getTag() != SIDE_FRAGMENT_TAG_SHOW) { 1129 mHideMainMenu.showAndHide(); 1130 } 1131 if (mSidePanelContainer.getTag() == SIDE_FRAGMENT_TAG_SHOW) { 1132 mHideSideFragment.showAndHide(); 1133 } 1134 } 1135 1136 @Override 1137 public boolean onTouchEvent(MotionEvent event) { 1138 if (mMainMenuView.getVisibility() != View.VISIBLE) { 1139 mGestureDetector.onTouchEvent(event); 1140 } 1141 return super.onTouchEvent(event); 1142 } 1143 1144 public void togglePipView() { 1145 enablePipView(!mPipEnabled); 1146 } 1147 1148 public void enablePipView(boolean enable) { 1149 if (enable == mPipEnabled) { 1150 return; 1151 } 1152 if (enable) { 1153 long pipChannelId = mTvView.getCurrentChannelId(); 1154 if (pipChannelId != Channel.INVALID_ID) { 1155 mPipEnabled = true; 1156 mPipChannelId = pipChannelId; 1157 startPip(); 1158 } 1159 } else { 1160 mPipEnabled = false; 1161 mPipChannelId = Channel.INVALID_ID; 1162 stopPip(); 1163 } 1164 } 1165 1166 private boolean dispatchKeyEventToSession(final KeyEvent event) { 1167 if (DEBUG) Log.d(TAG, "dispatchKeyEventToSession(" + event + ")"); 1168 if (mTvView != null) { 1169 return mTvView.dispatchKeyEvent(event); 1170 } 1171 return false; 1172 } 1173 1174 public void moveToChannel(long id) { 1175 if (mChannelMap != null && mChannelMap.isLoadFinished() 1176 && id != mChannelMap.getCurrentChannelId()) { 1177 if (mChannelMap.moveToChannel(id)) { 1178 tune(); 1179 } else if (!TextUtils.isEmpty(Utils.getInputIdForChannel(this, id))) { 1180 startTv(id); 1181 } else { 1182 Toast.makeText(this, R.string.input_is_not_available, Toast.LENGTH_SHORT).show(); 1183 } 1184 } 1185 } 1186 1187 private void channelUp() { 1188 if (mChannelMap != null && mChannelMap.isLoadFinished()) { 1189 if (mChannelMap.moveToNextChannel()) { 1190 tune(); 1191 } else { 1192 Toast.makeText(this, R.string.input_is_not_available, Toast.LENGTH_SHORT).show(); 1193 } 1194 } 1195 } 1196 1197 private void channelDown() { 1198 if (mChannelMap != null && mChannelMap.isLoadFinished()) { 1199 if (mChannelMap.moveToPreviousChannel()) { 1200 tune(); 1201 } else { 1202 Toast.makeText(this, R.string.input_is_not_available, Toast.LENGTH_SHORT).show(); 1203 } 1204 } 1205 } 1206 1207 public void showDialogFragment(final String tag, final DialogFragment dialog) { 1208 // A tag for dialog must be added to AVAILABLE_DIALOG_TAGS to make it launchable from TV. 1209 if (!AVAILABLE_DIALOG_TAGS.contains(tag)) { 1210 return; 1211 } 1212 mHandler.post(new Runnable() { 1213 @Override 1214 public void run() { 1215 FragmentManager fm = getFragmentManager(); 1216 fm.executePendingTransactions(); 1217 1218 for (String availableTag : AVAILABLE_DIALOG_TAGS) { 1219 if (fm.findFragmentByTag(availableTag) != null) { 1220 return; 1221 } 1222 } 1223 1224 FragmentTransaction ft = getFragmentManager().beginTransaction(); 1225 ft.addToBackStack(null); 1226 dialog.show(ft, tag); 1227 } 1228 }); 1229 } 1230 1231 public boolean isClosedCaptionEnabled() { 1232 return mIsClosedCaptionEnabled; 1233 } 1234 1235 public void setClosedCaptionEnabled(boolean enable, boolean storeInPreference) { 1236 mIsClosedCaptionEnabled = enable; 1237 if (storeInPreference) { 1238 mSharedPreferences.edit().putBoolean(TvSettings.PREF_CLOSED_CAPTION_ENABLED, enable) 1239 .apply(); 1240 } 1241 // TODO: send the change to TIS 1242 } 1243 1244 public void restoreClosedCaptionEnabled() { 1245 setClosedCaptionEnabled(mSharedPreferences.getBoolean( 1246 TvSettings.PREF_CLOSED_CAPTION_ENABLED, false), false); 1247 } 1248 1249 // Returns a constant defined in DisplayMode. 1250 public int getDisplayMode() { 1251 return mDisplayMode; 1252 } 1253 1254 public void setDisplayMode(int displayMode, boolean storeInPreference) { 1255 mDisplayMode = displayMode; 1256 if (storeInPreference) { 1257 mSharedPreferences.edit().putInt(TvSettings.PREF_DISPLAY_MODE, displayMode).apply(); 1258 } 1259 applyDisplayMode(mTvView.getVideoWidth(), mTvView.getVideoHeight()); 1260 } 1261 1262 public void restoreDisplayMode() { 1263 setDisplayMode(mSharedPreferences.getInt(TvSettings.PREF_DISPLAY_MODE, 1264 DisplayMode.MODE_NORMAL), false); 1265 } 1266 1267 // Returns a constant defined in TvSettings. 1268 public int getPipLocation() { 1269 return mPipLocation; 1270 } 1271 1272 public void setPipLocation(int pipLocation, boolean storeInPreference) { 1273 mPipLocation = pipLocation; 1274 if (storeInPreference) { 1275 mSharedPreferences.edit().putInt(TvSettings.PREF_PIP_LOCATION, pipLocation).apply(); 1276 } 1277 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPipView.getLayoutParams(); 1278 if (mPipLocation == TvSettings.PIP_LOCATION_TOP_LEFT) { 1279 lp.gravity = Gravity.TOP | Gravity.LEFT; 1280 } else if (mPipLocation == TvSettings.PIP_LOCATION_TOP_RIGHT) { 1281 lp.gravity = Gravity.TOP | Gravity.RIGHT; 1282 } else if (mPipLocation == TvSettings.PIP_LOCATION_BOTTOM_LEFT) { 1283 lp.gravity = Gravity.BOTTOM | Gravity.LEFT; 1284 } else if (mPipLocation == TvSettings.PIP_LOCATION_BOTTOM_RIGHT) { 1285 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 1286 } else { 1287 throw new IllegalArgumentException("Invaild PIP location: " + pipLocation); 1288 } 1289 mPipView.setLayoutParams(lp); 1290 } 1291 1292 public void restorePipLocation() { 1293 setDisplayMode(mSharedPreferences.getInt(TvSettings.PREF_PIP_LOCATION, 1294 TvSettings.PIP_LOCATION_BOTTOM_RIGHT), false); 1295 } 1296 1297 private class HideRunnable implements Runnable { 1298 private final View mView; 1299 private final long mWaitingTime; 1300 private boolean mOnHideAnimation; 1301 private final Runnable mPreShowListener; 1302 private final Runnable mPostHideListener; 1303 private boolean mHasFocusDuringHideAnimation; 1304 1305 private HideRunnable(View view, long waitingTime) { 1306 this(view, waitingTime, null, null); 1307 } 1308 1309 private HideRunnable(View view, long waitingTime, Runnable preShowListener, 1310 Runnable postHideListener) { 1311 mView = view; 1312 mWaitingTime = waitingTime; 1313 mPreShowListener = preShowListener; 1314 mPostHideListener = postHideListener; 1315 } 1316 1317 @Override 1318 public void run() { 1319 startHideAnimation(false); 1320 } 1321 1322 private boolean hasFocus() { 1323 return mView.getVisibility() == View.VISIBLE 1324 && (!mOnHideAnimation || mHasFocusDuringHideAnimation); 1325 } 1326 1327 private void startHideAnimation(boolean fastFadeOutRequired) { 1328 mOnHideAnimation = true; 1329 mHasFocusDuringHideAnimation = !fastFadeOutRequired; 1330 Animation anim = AnimationUtils.loadAnimation(TvActivity.this, 1331 android.R.anim.fade_out); 1332 anim.setInterpolator(AnimationUtils.loadInterpolator(TvActivity.this, 1333 android.R.interpolator.fast_out_linear_in)); 1334 if (fastFadeOutRequired) { 1335 anim.setDuration(mShortAnimationDuration); 1336 } 1337 anim.setAnimationListener(new Animation.AnimationListener() { 1338 @Override 1339 public void onAnimationStart(Animation animation) { 1340 } 1341 1342 @Override 1343 public void onAnimationRepeat(Animation animation) { 1344 } 1345 1346 @Override 1347 public void onAnimationEnd(Animation animation) { 1348 if (mOnHideAnimation) { 1349 hideView(); 1350 } 1351 } 1352 }); 1353 1354 mView.clearAnimation(); 1355 mView.startAnimation(anim); 1356 } 1357 1358 private void hideView() { 1359 mOnHideAnimation = false; 1360 mHasFocusDuringHideAnimation = false; 1361 mView.setVisibility(View.GONE); 1362 if (mPostHideListener != null) { 1363 mPostHideListener.run(); 1364 } 1365 } 1366 1367 private void hideImmediately(boolean withAnimation) { 1368 if (mView.getVisibility() != View.VISIBLE) { 1369 return; 1370 } 1371 if (!withAnimation) { 1372 mHandler.removeCallbacks(this); 1373 hideView(); 1374 mView.clearAnimation(); 1375 return; 1376 } 1377 if (!mOnHideAnimation) { 1378 mHandler.removeCallbacks(this); 1379 startHideAnimation(true); 1380 } 1381 } 1382 1383 private void showAndHide() { 1384 if (mView.getVisibility() != View.VISIBLE) { 1385 if (mPreShowListener != null) { 1386 mPreShowListener.run(); 1387 } 1388 mView.setVisibility(View.VISIBLE); 1389 Animation anim = AnimationUtils.loadAnimation(TvActivity.this, 1390 android.R.anim.fade_in); 1391 anim.setInterpolator(AnimationUtils.loadInterpolator(TvActivity.this, 1392 android.R.interpolator.linear_out_slow_in)); 1393 mView.clearAnimation(); 1394 mView.startAnimation(anim); 1395 } 1396 // Schedule the hide animation after a few seconds. 1397 mHandler.removeCallbacks(this); 1398 if (mOnHideAnimation) { 1399 mOnHideAnimation = false; 1400 mView.clearAnimation(); 1401 mView.setAlpha(1f); 1402 } 1403 mHandler.postDelayed(this, mWaitingTime); 1404 } 1405 } 1406 1407 private void setShynessMode(boolean shyMode) { 1408 mIsShy = shyMode; 1409 Intent intent = new Intent(LEANBACK_SET_SHYNESS_BROADCAST); 1410 intent.putExtra(LEANBACK_SHY_MODE_EXTRA, shyMode); 1411 sendBroadcast(intent); 1412 } 1413 1414 private boolean isShyModeSet() { 1415 return mIsShy; 1416 } 1417} 1418