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