TvActivity.java revision e7236b5a46375618f553c3b54a90c89cf77088ab
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.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.app.Activity; 22import android.app.AlertDialog; 23import android.app.DialogFragment; 24import android.app.Fragment; 25import android.app.FragmentTransaction; 26import android.content.ComponentName; 27import android.content.ContentUris; 28import android.content.Context; 29import android.content.Intent; 30import android.content.pm.PackageManager; 31import android.content.pm.ResolveInfo; 32import android.database.ContentObserver; 33import android.graphics.Point; 34import android.media.AudioManager; 35import android.net.Uri; 36import android.os.Bundle; 37import android.os.Handler; 38import android.provider.TvContract; 39import android.tv.TvInputInfo; 40import android.tv.TvInputManager; 41import android.tv.TvInputService; 42import android.tv.TvView; 43import android.util.Log; 44import android.view.Display; 45import android.view.GestureDetector; 46import android.view.InputEvent; 47import android.view.KeyEvent; 48import android.view.MotionEvent; 49import android.view.SurfaceHolder; 50import android.view.View; 51import android.view.ViewGroup; 52import android.widget.LinearLayout; 53import android.widget.TextView; 54import android.widget.Toast; 55 56import com.android.tv.menu.MenuDialogFragment; 57 58import java.util.List; 59 60/** 61 * The main activity for demonstrating TV app. 62 */ 63public class TvActivity extends Activity implements 64 InputPickerDialogFragment.InputPickerDialogListener, 65 AudioManager.OnAudioFocusChangeListener { 66 // STOPSHIP: Turn debugging off 67 private static final boolean DEBUG = true; 68 private static final String TAG = "TvActivity"; 69 70 private static final int DURATION_SHOW_CHANNEL_BANNER = 2000; 71 private static final int DURATION_SHOW_CONTROL_GUIDE = 1000; 72 private static final float AUDIO_MAX_VOLUME = 1.0f; 73 private static final float AUDIO_MIN_VOLUME = 0.0f; 74 private static final float AUDIO_DUCKING_VOLUME = 0.3f; 75 private static final int DELAY_FOR_SURFACE_RELEASE = 300; 76 // TODO: add more KEYCODEs to the white list. 77 private static final int[] KEYCODE_WHITELIST = { 78 KeyEvent.KEYCODE_0, KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3, 79 KeyEvent.KEYCODE_4, KeyEvent.KEYCODE_5, KeyEvent.KEYCODE_5, KeyEvent.KEYCODE_7, 80 KeyEvent.KEYCODE_8, KeyEvent.KEYCODE_9, KeyEvent.KEYCODE_STAR, KeyEvent.KEYCODE_POUND, 81 KeyEvent.KEYCODE_M, 82 }; 83 // TODO: this value should be able to be toggled in menu. 84 private static final boolean USE_KEYCODE_BLACKLIST = false; 85 private static final int[] KEYCODE_BLACKLIST = { 86 KeyEvent.KEYCODE_MENU, KeyEvent.KEYCODE_CHANNEL_UP, KeyEvent.KEYCODE_CHANNEL_DOWN, 87 KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT 88 }; 89 // STOPSHIP: debug keys are used only for testing. 90 private static final boolean USE_DEBUG_KEYS = true; 91 92 private static final int REQUEST_START_SETUP_ACTIIVTY = 0; 93 94 private static final String LEANBACK_SET_SHYNESS_BROADCAST = 95 "com.android.mclauncher.action.SET_APP_SHYNESS"; 96 private static final String LEANBACK_SHY_MODE_EXTRA = "shyMode"; 97 98 private TvInputManager mTvInputManager; 99 private TvView mTvView; 100 private LinearLayout mControlGuide; 101 private LinearLayout mChannelBanner; 102 private Runnable mHideChannelBanner; 103 private Runnable mHideControlGuide; 104 private TextView mChannelTextView; 105 private TextView mProgramTextView; 106 private int mShortAnimationDuration; 107 private int mDisplayWidth; 108 private GestureDetector mGestureDetector; 109 private ChannelMap mChannelMap; 110 private TvInputManager.Session mTvSession; 111 private TvInputInfo mTvInputInfo; 112 private TvInputInfo mTvInputInfoForSetup; 113 private AudioManager mAudioManager; 114 private int mAudioFocusStatus; 115 private final Handler mHandler = new Handler(); 116 private boolean mDefaultSessionRequested; 117 private boolean mTunePendding; 118 private boolean mPipShowing; 119 private boolean mDebugNonFullSizeScreen; 120 private boolean mUseKeycodeBlacklist = USE_KEYCODE_BLACKLIST; 121 private boolean mIsShy = true; 122 123 private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { 124 @Override 125 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } 126 127 @Override 128 public void surfaceCreated(SurfaceHolder holder) { } 129 130 @Override 131 public void surfaceDestroyed(SurfaceHolder holder) { 132 // TODO: It is a hack to wait to release a surface at TIS. If there is a way to 133 // know when the surface is released at TIS, we don't need this hack. 134 try { 135 if (DEBUG) Log.d(TAG, "Sleep to wait destroying a surface"); 136 Thread.sleep(DELAY_FOR_SURFACE_RELEASE); 137 if (DEBUG) Log.d(TAG, "Wake up from sleeping"); 138 } catch (InterruptedException e) { 139 e.printStackTrace(); 140 } 141 } 142 }; 143 144 // PIP is used for debug/verification of multiple sessions rather than real PIP feature. 145 // When PIP is enabled, the same channel as mTvView is tuned. 146 private TvView mPipView; 147 private TvInputManager.Session mPipSession; 148 private TvInputInfo mPipInputInfo; 149 150 @Override 151 protected void onCreate(Bundle savedInstanceState) { 152 super.onCreate(savedInstanceState); 153 154 setContentView(R.layout.activity_tv); 155 mTvView = (TvView) findViewById(R.id.tv_view); 156 mTvView.setOnUnhandledInputEventListener(new TvView.OnUnhandledInputEventListener() { 157 @Override 158 public boolean onUnhandledInputEvent(InputEvent event) { 159 if (event instanceof KeyEvent) { 160 KeyEvent keyEvent = (KeyEvent) event; 161 if (keyEvent.getAction() == KeyEvent.ACTION_UP) { 162 return onKeyUp(keyEvent.getKeyCode(), keyEvent); 163 } 164 } else if (event instanceof MotionEvent) { 165 MotionEvent motionEvent = (MotionEvent) event; 166 if (motionEvent.isTouchEvent()) { 167 return onTouchEvent(motionEvent); 168 } 169 } 170 return false; 171 } 172 }); 173 mTvView.getHolder().addCallback(mSurfaceHolderCallback); 174 mPipView = (TvView) findViewById(R.id.pip_view); 175 mPipView.setZOrderMediaOverlay(true); 176 mPipView.getHolder().addCallback(mSurfaceHolderCallback); 177 178 mControlGuide = (LinearLayout) findViewById(R.id.control_guide); 179 mChannelBanner = (LinearLayout) findViewById(R.id.channel_banner); 180 181 // Initially hide the channel banner and the control guide. 182 mChannelBanner.setVisibility(View.GONE); 183 mControlGuide.setVisibility(View.GONE); 184 185 mHideControlGuide = new HideRunnable(mControlGuide); 186 mHideChannelBanner = new HideRunnable(mChannelBanner); 187 188 mChannelTextView = (TextView) findViewById(R.id.channel_text); 189 mProgramTextView = (TextView) findViewById(R.id.program_text); 190 191 mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); 192 193 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 194 mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; 195 Display display = getWindowManager().getDefaultDisplay(); 196 Point size = new Point(); 197 display.getSize(size); 198 mDisplayWidth = size.x; 199 200 mGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { 201 static final float CONTROL_MARGIN = 0.2f; 202 final float mLeftMargin = mDisplayWidth * CONTROL_MARGIN; 203 final float mRightMargin = mDisplayWidth * (1 - CONTROL_MARGIN); 204 205 @Override 206 public boolean onDown(MotionEvent event) { 207 Log.d(TAG, "onDown: " + event.toString()); 208 if (mChannelMap == null) { 209 return false; 210 } 211 212 showAndHide(mControlGuide, mHideControlGuide, DURATION_SHOW_CONTROL_GUIDE); 213 214 if (event.getX() <= mLeftMargin) { 215 channelDown(); 216 return true; 217 } else if (event.getX() >= mRightMargin) { 218 channelUp(); 219 return true; 220 } 221 return false; 222 } 223 224 @Override 225 public boolean onSingleTapUp(MotionEvent event) { 226 if (mChannelMap == null) { 227 showInputPickerDialog(); 228 return true; 229 } 230 231 if (event.getX() > mLeftMargin && event.getX() < mRightMargin) { 232 showMenu(); 233 return true; 234 } 235 return false; 236 } 237 }); 238 239 getContentResolver().registerContentObserver(TvContract.Programs.CONTENT_URI, true, 240 mProgramUpdateObserver); 241 242 mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); 243 } 244 245 @Override 246 protected void onNewIntent(Intent intent) { 247 setIntent(intent); 248 } 249 250 @Override 251 protected void onResume() { 252 super.onResume(); 253 startDefaultSession(); 254 } 255 256 private void startDefaultSession() { 257 if (mTvInputManager == null) { 258 return; 259 } 260 // TODO: Remove this check after TvInputManagerService becomes system service. 261 if (mDefaultSessionRequested) { 262 return; 263 } 264 mDefaultSessionRequested = true; 265 if (mTunePendding) { 266 return; 267 } 268 269 // Check whether the system has at least one TvInputService app installed. 270 final List<ResolveInfo> services = getPackageManager().queryIntentServices( 271 new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_SERVICES); 272 if (services == null || services.isEmpty()) { 273 Toast.makeText(this, R.string.no_input_device_found, Toast.LENGTH_SHORT).show(); 274 // TODO: Direct the user to a Play Store landing page for TvInputService apps. 275 return; 276 } 277 278 // Figure out the initial channel to tune to. 279 long channelId = Channel.INVALID_ID; 280 Intent intent = getIntent(); 281 if (Intent.ACTION_VIEW.equals(intent.getAction())) { 282 // In case the channel is given explicitly, use it. 283 channelId = ContentUris.parseId(intent.getData()); 284 } 285 if (channelId == Channel.INVALID_ID) { 286 // Otherwise, remember the last channel the user watched. 287 channelId = TvInputUtils.getLastWatchedChannelId(this); 288 } 289 if (channelId == Channel.INVALID_ID) { 290 // If failed to pick a channel, try a different input. 291 showInputPickerDialog(); 292 return; 293 } 294 ComponentName inputName = TvInputUtils.getInputNameForChannel(this, channelId); 295 if (inputName == null) { 296 // If failed to determine the input for that channel, try a different input. 297 showInputPickerDialog(); 298 return; 299 } 300 // If the session is already started, simply adjust the volume without tune. 301 if (mTvSession != null) { 302 setVolumeByAudioFocusStatus(); 303 } else { 304 List<TvInputInfo> inputList = mTvInputManager.getTvInputList(); 305 for (TvInputInfo info : inputList) { 306 if (inputName.equals(info.getComponent())) { 307 startSession(info, channelId); 308 break; 309 } 310 } 311 } 312 } 313 314 @Override 315 protected void onStop() { 316 if (DEBUG) Log.d(TAG, "onStop() -- stop all sessions"); 317 stopSession(); 318 stopPipSession(); 319 mDefaultSessionRequested = false; 320 super.onStop(); 321 } 322 323 public void showInputPickerDialog() { 324 showDialogFragment(InputPickerDialogFragment.DIALOG_TAG, new InputPickerDialogFragment()); 325 } 326 327 @Override 328 public void onInputPicked(final TvInputInfo selectedTvInput, final String displayName) { 329 if (mTvSession != null && selectedTvInput.equals(mTvInputInfo)) { 330 // Nothing has changed thus nothing to do. 331 return; 332 } 333 334 if (!TvInputUtils.hasChannel(this, selectedTvInput)) { 335 mTvInputInfoForSetup = null; 336 if (showSetupActivity(selectedTvInput, displayName)) { 337 stopSession(); 338 } 339 return; 340 } 341 342 // Start a new session with the new input. 343 stopSession(); 344 345 // TODO: It is a hack to wait to release a surface at TIS. If there is a way to 346 // know when the surface is released at TIS, we don't need this hack. 347 mHandler.postDelayed(new Runnable() { 348 @Override 349 public void run() { 350 startSession(selectedTvInput); 351 } 352 }, DELAY_FOR_SURFACE_RELEASE); 353 } 354 355 private boolean showSetupActivity(TvInputInfo inputInfo, String displayName) { 356 PackageManager pm = getPackageManager(); 357 List<ResolveInfo> activityInfos = pm.queryIntentActivities( 358 new Intent(TvInputUtils.ACTION_SETUP), PackageManager.GET_ACTIVITIES); 359 ResolveInfo setupActivity = null; 360 if (activityInfos != null) { 361 for (ResolveInfo info : activityInfos) { 362 if (info.activityInfo.packageName.equals(inputInfo.getPackageName())) { 363 setupActivity = info; 364 } 365 } 366 } 367 368 if (setupActivity == null) { 369 String message = String.format(getString(R.string.input_setup_activity_not_found), 370 displayName); 371 new AlertDialog.Builder(this) 372 .setMessage(message) 373 .setPositiveButton(R.string.OK, null) 374 .show(); 375 return false; 376 } 377 378 mTvInputInfoForSetup = inputInfo; 379 Intent intent = new Intent(TvInputUtils.ACTION_SETUP); 380 intent.setClassName(setupActivity.activityInfo.packageName, 381 setupActivity.activityInfo.name); 382 startActivityForResult(intent, REQUEST_START_SETUP_ACTIIVTY); 383 384 return true; 385 } 386 387 @Override 388 public void onActivityResult(int requestCode, int resultCode, Intent data) { 389 switch (requestCode) { 390 case REQUEST_START_SETUP_ACTIIVTY: 391 if (resultCode == Activity.RESULT_OK && mTvInputInfoForSetup != null) { 392 startSession(mTvInputInfoForSetup); 393 } 394 break; 395 396 default: 397 //TODO: Handle failure of setup. 398 } 399 mTvInputInfoForSetup = null; 400 } 401 402 @Override 403 public boolean dispatchKeyEvent(KeyEvent event) { 404 if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")"); 405 int eventKeyCode = event.getKeyCode(); 406 if (mUseKeycodeBlacklist) { 407 for (int keycode : KEYCODE_BLACKLIST) { 408 if (keycode == eventKeyCode) { 409 return super.dispatchKeyEvent(event); 410 } 411 } 412 return dispatchKeyEventToSession(event); 413 } else { 414 for (int keycode : KEYCODE_WHITELIST) { 415 if (keycode == eventKeyCode) { 416 return dispatchKeyEventToSession(event); 417 } 418 } 419 return super.dispatchKeyEvent(event); 420 } 421 } 422 423 @Override 424 public void onAudioFocusChange(int focusChange) { 425 mAudioFocusStatus = focusChange; 426 setVolumeByAudioFocusStatus(); 427 } 428 429 private void setVolumeByAudioFocusStatus() { 430 if (mTvSession != null) { 431 switch (mAudioFocusStatus) { 432 case AudioManager.AUDIOFOCUS_GAIN: 433 mTvSession.setVolume(AUDIO_MAX_VOLUME); 434 break; 435 case AudioManager.AUDIOFOCUS_LOSS: 436 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 437 mTvSession.setVolume(AUDIO_MIN_VOLUME); 438 break; 439 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 440 mTvSession.setVolume(AUDIO_DUCKING_VOLUME); 441 break; 442 } 443 } 444 } 445 446 private void startSession(TvInputInfo selectedTvInput) { 447 long channelId = TvInputUtils.getLastWatchedChannelId(TvActivity.this, 448 selectedTvInput.getId()); 449 startSession(selectedTvInput, channelId); 450 } 451 452 private void startSession(TvInputInfo inputInfo, long channelId) { 453 // TODO: recreate SurfaceView to prevent abusing from the previous session. 454 mTvInputInfo = inputInfo; 455 // Prepare a new channel map for the current input. 456 mChannelMap = new ChannelMap(this, inputInfo.getComponent(), channelId, 457 mOnChannelsLoadFinished); 458 // Create a new session and start. 459 mTvView.bindTvInput(inputInfo.getComponent(), mSessionCreated); 460 tune(); 461 } 462 463 private final TvInputManager.SessionCreateCallback mSessionCreated = 464 new TvInputManager.SessionCreateCallback() { 465 @Override 466 public void onSessionCreated(TvInputManager.Session session) { 467 if (session != null) { 468 mTvSession = session; 469 int result = mAudioManager.requestAudioFocus(TvActivity.this, 470 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); 471 mAudioFocusStatus = 472 (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ? 473 AudioManager.AUDIOFOCUS_GAIN 474 : AudioManager.AUDIOFOCUS_LOSS; 475 if (mTunePendding) { 476 tune(); 477 } 478 } else { 479 Log.w(TAG, "Failed to create a session"); 480 // TODO: show something to user about this error. 481 } 482 } 483 }; 484 485 private void startPipSession() { 486 if (mTvSession == null) { 487 Log.w(TAG, "TV content should be playing."); 488 return; 489 } 490 Log.d(TAG, "startPipSession"); 491 mPipInputInfo = mTvInputInfo; 492 mPipView.bindTvInput(mPipInputInfo.getComponent(), mPipSessionCreated); 493 mPipShowing = true; 494 } 495 496 private final TvInputManager.SessionCreateCallback mPipSessionCreated = 497 new TvInputManager.SessionCreateCallback() { 498 @Override 499 public void onSessionCreated(final TvInputManager.Session session) { 500 Log.d(TAG, "PIP session is created."); 501 if (mTvSession == null) { 502 Log.w(TAG, "TV content should be playing."); 503 if (session != null) { 504 mPipView.unbindTvInput(); 505 } 506 mPipShowing = false; 507 return; 508 } 509 if (session == null) { 510 Log.w(TAG, "Fail to create another session."); 511 mPipShowing = false; 512 return; 513 } 514 runOnUiThread(new Runnable() { 515 @Override 516 public void run() { 517 mPipSession = session; 518 mPipSession.setVolume(0); 519 mPipSession.tune(mChannelMap.getCurrentChannelUri()); 520 mPipView.setVisibility(View.VISIBLE); 521 } 522 }); 523 } 524 }; 525 526 private final ContentObserver mProgramUpdateObserver = new ContentObserver(new Handler()) { 527 @Override 528 public void onChange(boolean selfChange, Uri uri) { 529 if (mChannelMap == null || !mChannelMap.isLoadFinished()) { 530 return; 531 } 532 Uri channelUri = mChannelMap.getCurrentChannelUri(); 533 if (channelUri == null) { 534 return; 535 } 536 Program program = TvInputUtils.getCurrentProgram(TvActivity.this, channelUri); 537 if (program == null) { 538 return; 539 } 540 mProgramTextView.setText(program.getTitle()); 541 } 542 }; 543 544 private final Runnable mOnChannelsLoadFinished = new Runnable() { 545 @Override 546 public void run() { 547 if (mTunePendding) { 548 tune(); 549 } 550 } 551 }; 552 553 private void tune() { 554 Log.d(TAG, "tune()"); 555 // Prerequisites to be able to tune. 556 if (mChannelMap == null || !mChannelMap.isLoadFinished()) { 557 Log.d(TAG, "Channel map not ready"); 558 mTunePendding = true; 559 return; 560 } 561 if (mTvSession == null) { 562 Log.d(TAG, "Service not connected"); 563 mTunePendding = true; 564 return; 565 } 566 setVolumeByAudioFocusStatus(); 567 568 Uri currentChannelUri = mChannelMap.getCurrentChannelUri(); 569 if (currentChannelUri != null) { 570 // TODO: implement 'no signal' 571 // TODO: add result callback and show a message on failure. 572 TvInputUtils.setLastWatchedChannel(this, mTvInputInfo.getId(), currentChannelUri); 573 mTvSession.tune(currentChannelUri); 574 if (isShyModeSet()) { 575 setShynessMode(false); 576 // TODO: Set the shy mode to true when tune() fails. 577 } 578 displayChannelBanner(); 579 } 580 mTunePendding = false; 581 } 582 583 private void displayChannelBanner() { 584 runOnUiThread(new Runnable() { 585 @Override 586 public void run() { 587 if (mChannelMap == null || !mChannelMap.isLoadFinished()) { 588 return; 589 } 590 591 // TODO: Show a beautiful channel banner instead. 592 String channelBannerString = ""; 593 String displayNumber = mChannelMap.getCurrentDisplayNumber(); 594 if (displayNumber != null) { 595 channelBannerString += displayNumber; 596 } 597 String displayName = mChannelMap.getCurrentDisplayName(); 598 if (displayName != null) { 599 channelBannerString += " " + displayName; 600 } 601 mChannelTextView.setText(channelBannerString); 602 603 Program program = TvInputUtils.getCurrentProgram(TvActivity.this, 604 mChannelMap.getCurrentChannelUri()); 605 String programTitle = program != null ? program.getTitle() : null; 606 // Program title might not be available at this point. Setting the text to null to 607 // clear the previous program title for now. It will be filled as soon as we get the 608 // updated program information. 609 mProgramTextView.setText(programTitle); 610 611 showAndHide(mChannelBanner, mHideChannelBanner, DURATION_SHOW_CHANNEL_BANNER); 612 } 613 }); 614 } 615 616 public void showRecentlyWatchedDialog() { 617 showDialogFragment(RecentlyWatchedDialogFragment.DIALOG_TAG, 618 new RecentlyWatchedDialogFragment()); 619 } 620 621 private void stopSession() { 622 if (mTvSession != null) { 623 mTvSession.setVolume(AUDIO_MIN_VOLUME); 624 mAudioManager.abandonAudioFocus(this); 625 mTvView.unbindTvInput(); 626 mTvSession = null; 627 mTvInputInfo = null; 628 } 629 if (mChannelMap != null) { 630 mChannelMap.close(); 631 mChannelMap = null; 632 } 633 } 634 635 private void stopPipSession() { 636 Log.d(TAG, "stopPipSession"); 637 if (mPipSession != null) { 638 mPipView.setVisibility(View.INVISIBLE); 639 mPipView.unbindTvInput(); 640 mPipSession = null; 641 mPipInputInfo = null; 642 } 643 mPipShowing = false; 644 } 645 646 @Override 647 protected void onSaveInstanceState(Bundle outState) { 648 // Do not save instance state because restoring instance state when TV app died 649 // unexpectedly can cause some problems like initializing fragments duplicately and 650 // accessing resource before it is initialzed. 651 } 652 653 @Override 654 protected void onDestroy() { 655 getContentResolver().unregisterContentObserver(mProgramUpdateObserver); 656 mTvView.getHolder().removeCallback(mSurfaceHolderCallback); 657 mPipView.getHolder().removeCallback(mSurfaceHolderCallback); 658 Log.d(TAG, "onDestroy()"); 659 super.onDestroy(); 660 } 661 662 @Override 663 public boolean onKeyUp(int keyCode, KeyEvent event) { 664 if (mChannelMap == null) { 665 switch (keyCode) { 666 case KeyEvent.KEYCODE_H: 667 showRecentlyWatchedDialog(); 668 return true; 669 case KeyEvent.KEYCODE_TV_INPUT: 670 case KeyEvent.KEYCODE_I: 671 case KeyEvent.KEYCODE_CHANNEL_UP: 672 case KeyEvent.KEYCODE_DPAD_UP: 673 case KeyEvent.KEYCODE_CHANNEL_DOWN: 674 case KeyEvent.KEYCODE_DPAD_DOWN: 675 case KeyEvent.KEYCODE_NUMPAD_ENTER: 676 case KeyEvent.KEYCODE_E: 677 case KeyEvent.KEYCODE_MENU: 678 showInputPickerDialog(); 679 return true; 680 } 681 } else { 682 switch (keyCode) { 683 case KeyEvent.KEYCODE_H: 684 showRecentlyWatchedDialog(); 685 return true; 686 687 case KeyEvent.KEYCODE_TV_INPUT: 688 case KeyEvent.KEYCODE_I: 689 showInputPickerDialog(); 690 return true; 691 692 case KeyEvent.KEYCODE_CHANNEL_UP: 693 case KeyEvent.KEYCODE_DPAD_UP: 694 channelUp(); 695 return true; 696 697 case KeyEvent.KEYCODE_CHANNEL_DOWN: 698 case KeyEvent.KEYCODE_DPAD_DOWN: 699 channelDown(); 700 return true; 701 702 case KeyEvent.KEYCODE_NUMPAD_ENTER: 703 case KeyEvent.KEYCODE_E: 704 displayChannelBanner(); 705 return true; 706 707 case KeyEvent.KEYCODE_DPAD_CENTER: 708 case KeyEvent.KEYCODE_MENU: 709 if (event.isCanceled()) { 710 return true; 711 } 712 showMenu(); 713 return true; 714 } 715 } 716 if (USE_DEBUG_KEYS) { 717 switch (keyCode) { 718 case KeyEvent.KEYCODE_W: { 719 mDebugNonFullSizeScreen = !mDebugNonFullSizeScreen; 720 if (mDebugNonFullSizeScreen) { 721 mTvView.layout(100, 100, 400, 300); 722 } else { 723 ViewGroup.LayoutParams params = mTvView.getLayoutParams(); 724 params.width = ViewGroup.LayoutParams.MATCH_PARENT; 725 params.height = ViewGroup.LayoutParams.MATCH_PARENT; 726 mTvView.setLayoutParams(params); 727 } 728 return true; 729 } 730 case KeyEvent.KEYCODE_P: { 731 togglePipView(); 732 return true; 733 } 734 case KeyEvent.KEYCODE_CTRL_LEFT: 735 case KeyEvent.KEYCODE_CTRL_RIGHT: { 736 mUseKeycodeBlacklist = !mUseKeycodeBlacklist; 737 return true; 738 } 739 } 740 } 741 return super.onKeyUp(keyCode, event); 742 } 743 744 @Override 745 public boolean onTouchEvent(MotionEvent event) { 746 mGestureDetector.onTouchEvent(event); 747 return super.onTouchEvent(event); 748 } 749 750 public void togglePipView() { 751 if (mPipShowing) { 752 stopPipSession(); 753 } else { 754 startPipSession(); 755 } 756 } 757 758 private boolean dispatchKeyEventToSession(final KeyEvent event) { 759 if (DEBUG) Log.d(TAG, "dispatchKeyEventToSession(" + event + ")"); 760 if (mTvView != null) { 761 return mTvView.dispatchKeyEvent(event); 762 } 763 return false; 764 } 765 766 private void channelUp() { 767 if (mChannelMap != null && mChannelMap.isLoadFinished()) { 768 mChannelMap.moveToNextChannel(); 769 tune(); 770 } 771 } 772 773 private void channelDown() { 774 if (mChannelMap != null && mChannelMap.isLoadFinished()) { 775 mChannelMap.moveToPreviousChannel(); 776 tune(); 777 } 778 } 779 780 private void showMenu() { 781 MenuDialogFragment f = new MenuDialogFragment(); 782 if (mTvSession != null) { 783 Bundle arg = new Bundle(); 784 arg.putString(MenuDialogFragment.ARG_CURRENT_PACKAGE_NAME, 785 mTvInputInfo.getPackageName()); 786 arg.putString(MenuDialogFragment.ARG_CURRENT_SERVICE_NAME, 787 mTvInputInfo.getServiceName()); 788 f.setArguments(arg); 789 } 790 791 showDialogFragment(MenuDialogFragment.DIALOG_TAG, f); 792 } 793 794 private void showDialogFragment(String tag, DialogFragment dialog) { 795 FragmentTransaction ft = getFragmentManager().beginTransaction(); 796 Fragment prev = getFragmentManager().findFragmentByTag(tag); 797 if (prev != null) { 798 ft.remove(prev); 799 } 800 ft.addToBackStack(null); 801 dialog.show(ft, tag); 802 } 803 804 private final Handler mHideHandler = new Handler(); 805 806 private class HideRunnable implements Runnable { 807 private final View mView; 808 809 public HideRunnable(View view) { 810 mView = view; 811 } 812 813 @Override 814 public void run() { 815 mView.animate() 816 .alpha(0f) 817 .setDuration(mShortAnimationDuration) 818 .setListener(new AnimatorListenerAdapter() { 819 @Override 820 public void onAnimationEnd(Animator animation) { 821 mView.setVisibility(View.GONE); 822 } 823 }); 824 } 825 } 826 827 private void showAndHide(View view, Runnable hide, long duration) { 828 if (view.getVisibility() == View.VISIBLE) { 829 // Skip the show animation if the view is already visible and cancel the scheduled hide 830 // animation. 831 mHideHandler.removeCallbacks(hide); 832 } else { 833 view.setAlpha(0f); 834 view.setVisibility(View.VISIBLE); 835 view.animate() 836 .alpha(1f) 837 .setDuration(mShortAnimationDuration) 838 .setListener(null); 839 } 840 // Schedule the hide animation after a few seconds. 841 mHideHandler.postDelayed(hide, duration); 842 } 843 844 private void setShynessMode(boolean shyMode) { 845 mIsShy = shyMode; 846 Intent intent = new Intent(LEANBACK_SET_SHYNESS_BROADCAST); 847 intent.putExtra(LEANBACK_SHY_MODE_EXTRA, shyMode); 848 sendBroadcast(intent); 849 } 850 851 private boolean isShyModeSet() { 852 return mIsShy; 853 } 854} 855