diff options
author | Youngsang Cho <youngsang@google.com> | 2014-05-19 12:27:46 +0900 |
---|---|---|
committer | Youngsang Cho <youngsang@google.com> | 2014-05-19 16:11:16 +0900 |
commit | 900cc7509f1edf86db9ba1e4b71b7d1096769a0a (patch) | |
tree | 19fb87adbb62826b9a464b6a39b69237d26fa732 /src/com/android/tv | |
parent | 3673f15e9c498fb7b4900997757c470869cb53e4 (diff) | |
download | TV-900cc7509f1edf86db9ba1e4b71b7d1096769a0a.tar.gz |
Add a customized TvView
- Move session-related works from TvActivity to TvView
Change-Id: I96dd7cb49e26298d6530bbf9566c39a5ad07774e
Diffstat (limited to 'src/com/android/tv')
-rw-r--r-- | src/com/android/tv/TunableTvView.java | 197 | ||||
-rw-r--r-- | src/com/android/tv/TvActivity.java | 398 | ||||
-rw-r--r-- | src/com/android/tv/TvInputManagerHelper.java | 7 | ||||
-rw-r--r-- | src/com/android/tv/Utils.java | 31 |
4 files changed, 363 insertions, 270 deletions
diff --git a/src/com/android/tv/TunableTvView.java b/src/com/android/tv/TunableTvView.java new file mode 100644 index 00000000..366ead76 --- /dev/null +++ b/src/com/android/tv/TunableTvView.java @@ -0,0 +1,197 @@ +package com.android.tv; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.tv.TvInputInfo; +import android.tv.TvInputManager; +import android.tv.TvView; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceHolder; + +import com.android.internal.util.Preconditions; + +public class TunableTvView extends TvView { + private static final boolean DEBUG = true; + private static final String TAG = "TunableTvView"; + + private static final int DELAY_FOR_SURFACE_RELEASE = 300; + private static final int MSG_TUNE = 0; + + private float mVolume; + private long mChannelId = Channel.INVALID_ID; + private TvInputManagerHelper mInputManagerHelper; + private boolean mStarted; + private TvInputInfo mInputInfo; + private TvInputManager.Session mSession; + private OnTuneListener mOnTuneListener; + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_TUNE: + Preconditions.checkState(mChannelId != Channel.INVALID_ID); + Preconditions.checkNotNull(mSession); + + mSession.tune(Utils.getChannelUri(mChannelId)); + if (mOnTuneListener != null) { + mOnTuneListener.onTuned(true, mChannelId); + } + break; + } + } + }; + + private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } + + @Override + public void surfaceCreated(SurfaceHolder holder) { } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + // TODO: It is a hack to wait to release a surface at TIS. If there is a way to + // know when the surface is released at TIS, we don't need this hack. + try { + if (DEBUG) Log.d(TAG, "Sleep to wait destroying a surface"); + Thread.sleep(DELAY_FOR_SURFACE_RELEASE); + if (DEBUG) Log.d(TAG, "Wake up from sleeping"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + + private final TvInputManager.SessionCallback mSessionCallback = + new TvInputManager.SessionCallback() { + @Override + public void onSessionCreated(TvInputManager.Session session) { + if (session != null) { + mSession = session; + mSession.setVolume(mVolume); + mHandler.sendEmptyMessage(MSG_TUNE); + } else { + Log.w(TAG, "Failed to create a session"); + long channelId = mChannelId; + mChannelId = Channel.INVALID_ID; + mInputInfo = null; + mSession = null; + if (mOnTuneListener != null) { + mOnTuneListener.onTuned(false, channelId); + mOnTuneListener = null; + } + } + } + + @Override + public void onSessionReleased(TvInputManager.Session session) { + Log.w(TAG, "Session is released by crash"); + long channelId = mChannelId; + mChannelId = Channel.INVALID_ID; + mInputInfo = null; + mSession = null; + if (mOnTuneListener != null) { + mOnTuneListener.onUnexpectedStop(channelId); + mOnTuneListener = null; + } + } + }; + + public TunableTvView(Context context) { + this(context, null, 0); + } + + public TunableTvView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TunableTvView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + getHolder().addCallback(mSurfaceHolderCallback); + } + + public void start(TvInputManagerHelper tvInputManagerHelper) { + mInputManagerHelper = tvInputManagerHelper; + if (mStarted) { + return; + } + mStarted = true; + } + + public void stop() { + if (!mStarted) { + return; + } + mHandler.removeMessages(MSG_TUNE); + mStarted = false; + mSession = null; + unbindTvInput(); + mChannelId = Channel.INVALID_ID; + mInputInfo = null; + mOnTuneListener = null; + } + + public boolean isPlaying() { + return mStarted; + } + + public boolean tuneTo(long channelId, OnTuneListener listener) { + if (!mStarted) { + throw new IllegalStateException("TvView isn't started"); + } + if (DEBUG) Log.d(TAG, "tuneTo " + channelId); + mHandler.removeMessages(MSG_TUNE); + String inputId = Utils.getInputIdForChannel(getContext(), channelId); + TvInputInfo inputInfo = mInputManagerHelper.getTvInputInfo(inputId); + if (inputInfo == null || !mInputManagerHelper.isAvailable(inputInfo)) { + return false; + } + mOnTuneListener = listener; + mChannelId = channelId; + if (!inputInfo.equals(mInputInfo)) { + mSession = null; + unbindTvInput(); + + // TODO: It is a hack to wait to release a surface at TIS. If there is a way to + // know when the surface is released at TIS, we don't need this hack. + try { + Thread.sleep(DELAY_FOR_SURFACE_RELEASE); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + mInputInfo = inputInfo; + bindTvInput(mInputInfo.getId(), mSessionCallback); + // mChannelId will be tuned after onSessionCreated. + } else { + mHandler.sendEmptyMessage(MSG_TUNE); + } + return true; + } + + public TvInputInfo getCurrentTvInputInfo() { + return mInputInfo; + } + + public long getCurrentChannelId() { + return mChannelId; + } + + public void setVolume(float volume) { + if (!mStarted) { + throw new IllegalStateException("TvView isn't started"); + } + if (DEBUG) Log.d(TAG, "setVolume " + volume); + mVolume = volume; + if (mSession != null) { + mSession.setVolume(volume); + } + } + + public interface OnTuneListener { + void onTuned(boolean success, long channelId); + void onUnexpectedStop(long channelId); + } +} diff --git a/src/com/android/tv/TvActivity.java b/src/com/android/tv/TvActivity.java index 9cfa7b34..36b199df 100644 --- a/src/com/android/tv/TvActivity.java +++ b/src/com/android/tv/TvActivity.java @@ -39,7 +39,6 @@ import android.text.TextUtils; import android.text.format.DateFormat; import android.tv.TvInputInfo; import android.tv.TvInputManager; -import android.tv.TvView; import android.util.Log; import android.view.Display; import android.view.GestureDetector; @@ -54,6 +53,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import com.android.tv.TunableTvView.OnTuneListener; import com.android.tv.ui.ChannelBannerView; import com.android.tv.ui.MainMenuView; @@ -70,7 +70,7 @@ public class TvActivity extends Activity implements private static final boolean DEBUG = true; private static final String TAG = "TvActivity"; - private static final int MSG_START_DEFAULT_SESSION_RETRY = 1; + private static final int MSG_START_TV_RETRY = 1; private static final int DURATION_SHOW_CHANNEL_BANNER = 2000; private static final int DURATION_SHOW_CONTROL_GUIDE = 1000; @@ -78,10 +78,10 @@ public class TvActivity extends Activity implements private static final float AUDIO_MAX_VOLUME = 1.0f; private static final float AUDIO_MIN_VOLUME = 0.0f; private static final float AUDIO_DUCKING_VOLUME = 0.3f; - private static final int DELAY_FOR_SURFACE_RELEASE = 300; - private static final int START_DEFAULT_SESSION_MAX_RETRY = 4; - private static final int START_DEFAULT_SESSION_RETRY_INTERVAL = 250; + private static final int START_TV_MAX_RETRY = 4; + private static final int START_TV_RETRY_INTERVAL = 250; private static final String PREF_KEY_IS_UNIFIED_TV_INPUT = "unified_tv_input"; + // TODO: add more KEYCODEs to the white list. private static final int[] KEYCODE_WHITELIST = { KeyEvent.KEYCODE_0, KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3, @@ -107,7 +107,7 @@ public class TvActivity extends Activity implements private static final HashSet<String> AVAILABLE_DIALOG_TAGS = new HashSet<String>(); private TvInputManager mTvInputManager; - private TvView mTvView; + private TunableTvView mTvView; private LinearLayout mControlGuide; private MainMenuView mMainMenuView; private ChannelBannerView mChannelBanner; @@ -119,7 +119,6 @@ public class TvActivity extends Activity implements private GestureDetector mGestureDetector; private ChannelMap mChannelMap; private long mInitChannelId; - private TvInputManager.Session mTvSession; private TvInputInfo mTvInputInfo; private TvInputInfo mTvInputInfoForSetup; private boolean mIsUnifiedTvInput; @@ -140,42 +139,19 @@ public class TvActivity extends Activity implements AVAILABLE_DIALOG_TAGS.add(PrivacySettingDialogFragment.DIALOG_TAG); } - private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } - - @Override - public void surfaceCreated(SurfaceHolder holder) { } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - // TODO: It is a hack to wait to release a surface at TIS. If there is a way to - // know when the surface is released at TIS, we don't need this hack. - try { - if (DEBUG) Log.d(TAG, "Sleep to wait destroying a surface"); - Thread.sleep(DELAY_FOR_SURFACE_RELEASE); - if (DEBUG) Log.d(TAG, "Wake up from sleeping"); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }; - // PIP is used for debug/verification of multiple sessions rather than real PIP feature. // When PIP is enabled, the same channel as mTvView is tuned. - private TvView mPipView; - private TvInputManager.Session mPipSession; - private TvInputInfo mPipInputInfo; + private TunableTvView mPipView; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { - if (msg.what == MSG_START_DEFAULT_SESSION_RETRY) { + if (msg.what == MSG_START_TV_RETRY) { Object[] arg = (Object[]) msg.obj; TvInputInfo input = (TvInputInfo) arg[0]; long channelId = (Long) arg[1]; int retryCount = msg.arg1; - startSessionIfAvailableOrRetry(input, channelId, retryCount); + startTvIfAvailableOrRetry(input, channelId, retryCount); } } }; @@ -185,8 +161,8 @@ public class TvActivity extends Activity implements super.onCreate(savedInstanceState); setContentView(R.layout.activity_tv); - mTvView = (TvView) findViewById(R.id.tv_view); - mTvView.setOnUnhandledInputEventListener(new TvView.OnUnhandledInputEventListener() { + mTvView = (TunableTvView) findViewById(R.id.tv_view); + mTvView.setOnUnhandledInputEventListener(new TunableTvView.OnUnhandledInputEventListener() { @Override public boolean onUnhandledInputEvent(InputEvent event) { if (event instanceof KeyEvent) { @@ -203,10 +179,8 @@ public class TvActivity extends Activity implements return false; } }); - mTvView.getHolder().addCallback(mSurfaceHolderCallback); - mPipView = (TvView) findViewById(R.id.pip_view); + mPipView = (TunableTvView) findViewById(R.id.pip_view); mPipView.setZOrderMediaOverlay(true); - mPipView.getHolder().addCallback(mSurfaceHolderCallback); mControlGuide = (LinearLayout) findViewById(R.id.control_guide); mChannelBanner = (ChannelBannerView) findViewById(R.id.channel_banner); @@ -303,13 +277,13 @@ public class TvActivity extends Activity implements // TODO: Direct the user to a Play Store landing page for TvInputService apps. return; } - startDefaultSession(mInitChannelId); + startTv(mInitChannelId); mInitChannelId = Channel.INVALID_ID; } - private void startDefaultSession(long channelId) { - if (mTvInputInfo != null) { - // A session has already started. + private void startTv(long channelId) { + if (mTvView.isPlaying()) { + // TV has already started. if (channelId == Channel.INVALID_ID) { // Simply adjust the volume without tune. setVolumeByAudioFocusStatus(); @@ -321,7 +295,7 @@ public class TvActivity extends Activity implements setVolumeByAudioFocusStatus(); return; } - stopSession(); + stopTv(); } if (channelId == Channel.INVALID_ID) { @@ -347,30 +321,30 @@ public class TvActivity extends Activity implements showInputPickerDialog(); return; } - startSessionIfAvailableOrRetry(input, channelId, 0); + startTvIfAvailableOrRetry(input, channelId, 0); } - private void startSessionIfAvailableOrRetry(TvInputInfo input, long channelId, int retryCount) { + private void startTvIfAvailableOrRetry(TvInputInfo input, long channelId, int retryCount) { if (!mTvInputManagerHelper.isAvailable(input.getId())) { - if (retryCount >= START_DEFAULT_SESSION_MAX_RETRY) { + if (retryCount >= START_TV_MAX_RETRY) { showInputPickerDialog(); return; } - if (DEBUG) Log.d(TAG, "Retry start session (retryCount=" + retryCount + ")"); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_DEFAULT_SESSION_RETRY, + if (DEBUG) Log.d(TAG, "Retry start TV (retryCount=" + retryCount + ")"); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TV_RETRY, retryCount + 1, 0, new Object[]{input, channelId}), - START_DEFAULT_SESSION_RETRY_INTERVAL); + START_TV_RETRY_INTERVAL); return; } - startSession(input, channelId); + startTv(input, channelId); } @Override protected void onStop() { - if (DEBUG) Log.d(TAG, "onStop() -- stop all sessions"); - mHandler.removeMessages(MSG_START_DEFAULT_SESSION_RETRY); - stopSession(); - stopPipSession(); + if (DEBUG) Log.d(TAG, "onStop()"); + mHandler.removeMessages(MSG_START_TV_RETRY); + stopTv(); + stopPip(); if (!isShyModeSet()) { setShynessMode(true); } @@ -386,44 +360,39 @@ public class TvActivity extends Activity implements return; } mIsUnifiedTvInput = true; - if (mTvInputInfo == null) { - Collection<TvInputInfo> inputs = mTvInputManagerHelper.getTvInputInfos(true); - if (inputs.isEmpty()) { - Toast.makeText(this, R.string.no_available_input_device, Toast.LENGTH_SHORT) - .show(); - } else { - TvInputInfo info = inputs.iterator().next(); - startSession(info); + if (mTvView.isPlaying()) { + TvInputInfo inputInfo = mTvView.getCurrentTvInputInfo(); + long channelId = mTvView.getCurrentChannelId(); + stopTv(); + if (channelId != Channel.INVALID_ID) { + startTv(inputInfo, channelId); + return; } - return; - } else { - // Restart session to re-create the channel map. - input = mTvInputInfo; } - } else { - if (mTvSession != null && input.equals(mTvInputInfo) && !mIsUnifiedTvInput) { - // Nothing has changed thus nothing to do. - return; - } - mIsUnifiedTvInput = false; - if (!Utils.hasChannel(this, input, false)) { - mTvInputInfoForSetup = null; - startSetupActivity(input); - return; + Collection<TvInputInfo> inputs = mTvInputManagerHelper.getTvInputInfos(true); + if (inputs.isEmpty()) { + Toast.makeText(this, R.string.no_available_input_device, Toast.LENGTH_SHORT) + .show(); + } else { + TvInputInfo info = inputs.iterator().next(); + startTvWithLastWatchedChannel(info); } + return; } - - // Start a new session with the new input. - stopSession(); - - // TODO: It is a hack to wait to release a surface at TIS. If there is a way to - // know when the surface is released at TIS, we don't need this hack. - try { - Thread.sleep(DELAY_FOR_SURFACE_RELEASE); - } catch (InterruptedException e) { - e.printStackTrace(); + if (input.equals(mTvInputInfo) && !mIsUnifiedTvInput) { + // Nothing has changed thus nothing to do. + return; + } + mIsUnifiedTvInput = false; + if (!Utils.hasChannel(this, input, false)) { + mTvInputInfoForSetup = null; + startSetupActivity(input); + return; } - startSession(input); + mTvInputInfo = input; + + stopTv(); + startTvWithLastWatchedChannel(input); } public void showEditChannelsDialog() { @@ -447,9 +416,6 @@ public class TvActivity extends Activity implements arg.putString(InputPickerDialogFragment.ARG_MAIN_INPUT_ID, mTvInputInfo.getId()); arg.putBoolean(InputPickerDialogFragment.ARG_IS_UNIFIED_TV_INPUT, mIsUnifiedTvInput); } - if (mPipInputInfo != null) { - arg.putString(InputPickerDialogFragment.ARG_SUB_INPUT_ID, mPipInputInfo.getId()); - } f.setArguments(arg); showDialogFragment(InputPickerDialogFragment.DIALOG_TAG, f); } @@ -471,7 +437,7 @@ public class TvActivity extends Activity implements if (Utils.startActivityForResult(this, input, Utils.ACTION_SETUP, REQUEST_START_SETUP_ACTIIVTY)) { mTvInputInfoForSetup = input; - stopSession(); + stopTv(); } else { String displayName = Utils.getDisplayNameForInput(this, input, false); String message = String.format(getString( @@ -488,7 +454,7 @@ public class TvActivity extends Activity implements switch (requestCode) { case REQUEST_START_SETUP_ACTIIVTY: if (resultCode == Activity.RESULT_OK && mTvInputInfoForSetup != null) { - startSession(mTvInputInfoForSetup); + startTvWithLastWatchedChannel(mTvInputInfoForSetup); } break; @@ -526,141 +492,101 @@ public class TvActivity extends Activity implements } private void setVolumeByAudioFocusStatus() { - if (mTvSession != null) { + if (mTvView.isPlaying()) { switch (mAudioFocusStatus) { case AudioManager.AUDIOFOCUS_GAIN: - mTvSession.setVolume(AUDIO_MAX_VOLUME); + mTvView.setVolume(AUDIO_MAX_VOLUME); break; case AudioManager.AUDIOFOCUS_LOSS: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - mTvSession.setVolume(AUDIO_MIN_VOLUME); + mTvView.setVolume(AUDIO_MIN_VOLUME); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - mTvSession.setVolume(AUDIO_DUCKING_VOLUME); + mTvView.setVolume(AUDIO_DUCKING_VOLUME); break; } } } - private void startSession(TvInputInfo inputInfo) { + private void startTvWithLastWatchedChannel(TvInputInfo inputInfo) { long channelId = Utils.getLastWatchedChannelId(TvActivity.this, inputInfo.getId()); - startSession(inputInfo, channelId); + startTv(inputInfo, channelId); } - private void startSession(TvInputInfo inputInfo, long channelId) { - if (mTvInputInfo != null || mChannelMap != null) { + private void startTv(TvInputInfo inputInfo, long channelId) { + if (mChannelMap != null) { // TODO: when this case occurs, we should remove the case. - Log.w(TAG, "The previous variables are not released in startSession"); - stopSession(); + Log.w(TAG, "The previous variables are not released in startTv"); + stopTv(); } - // TODO: recreate SurfaceView to prevent abusing from the previous session. - mTvInputInfo = inputInfo; - mMainMenuView.setChannelMap(null); + int result = mAudioManager.requestAudioFocus(TvActivity.this, + AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); + mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ? + AudioManager.AUDIOFOCUS_GAIN : AudioManager.AUDIOFOCUS_LOSS; // Prepare a new channel map for the current input. mChannelMap = new ChannelMap(this, mIsUnifiedTvInput ? null : inputInfo, channelId, mTvInputManagerHelper, mOnChannelsLoadFinished); - // Create a new session and start. - mTvView.bindTvInput(inputInfo.getId(), mSessionCreated); + mTvView.start(mTvInputManagerHelper); + setVolumeByAudioFocusStatus(); tune(); } - private void changeSession(TvInputInfo inputInfo) { - mTvSession = null; - mTvView.unbindTvInput(); - // TODO: It is a hack to wait to release a surface at TIS. If there is a way to - // know when the surface is released at TIS, we don't need this hack. - try { - Thread.sleep(DELAY_FOR_SURFACE_RELEASE); - } catch (InterruptedException e) { - e.printStackTrace(); + private void stopTv() { + if (mTvView.isPlaying()) { + mTvView.stop(); + mAudioManager.abandonAudioFocus(this); } - mTvInputInfo = inputInfo; - mTvView.bindTvInput(inputInfo.getId(), mSessionCreated); - tune(); + if (mChannelMap != null) { + mMainMenuView.setChannelMap(null); + mChannelMap.close(); + mChannelMap = null; + } + mTunePendding = false; } - private final TvInputManager.SessionCallback mSessionCreated = - new TvInputManager.SessionCallback() { - @Override - public void onSessionCreated(TvInputManager.Session session) { - if (session != null) { - mTvSession = session; - int result = mAudioManager.requestAudioFocus(TvActivity.this, - AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); - mAudioFocusStatus = - (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ? - AudioManager.AUDIOFOCUS_GAIN - : AudioManager.AUDIOFOCUS_LOSS; - if (mTunePendding) { - tune(); - } - } else { - Log.w(TAG, "Failed to create a session"); - // TODO: show something to user about this error. - } - } + private void startPip() { + if (!mTvView.isPlaying()) { + Log.w(TAG, "TV content should be playing"); + return; + } + if (DEBUG) Log.d(TAG, "startPip()"); + mPipView.start(mTvInputManagerHelper); + boolean success = mPipView.tuneTo(mChannelMap.getCurrentChannelId(), new OnTuneListener() { + @Override + public void onUnexpectedStop(long channelId) { + Log.w(TAG, "The PIP is Unexpectedly stopped"); + stopPip(); + } - @Override - public void onSessionReleased(TvInputManager.Session session) { - Log.w(TAG, "Session is released by crash"); - if (mTvSession == session) { - stopSession(); - startDefaultSession(Channel.INVALID_ID); - } + @Override + public void onTuned(boolean success, long channelId) { + if (!success) { + Log.w(TAG, "Fail to start the PIP during channel tunning"); + stopPip(); + } else { + mPipView.setVisibility(View.VISIBLE); } - }; - - private void startPipSession() { - if (mTvSession == null) { - Log.w(TAG, "TV content should be playing"); + } + }); + if (!success) { + Log.w(TAG, "Fail to start the PIP"); return; } - if (DEBUG) Log.d(TAG, "startPipSession()"); - mPipInputInfo = mTvInputInfo; - mPipView.bindTvInput(mPipInputInfo.getId(), mPipSessionCreated); + mPipView.setVolume(AUDIO_MIN_VOLUME); mPipShowing = true; } - private final TvInputManager.SessionCallback mPipSessionCreated = - new TvInputManager.SessionCallback() { - @Override - public void onSessionCreated(final TvInputManager.Session session) { - if (DEBUG) Log.d(TAG, "PIP session is created"); - if (mTvSession == null) { - Log.w(TAG, "TV content should be playing"); - if (session != null) { - mPipView.unbindTvInput(); - } - mPipShowing = false; - return; - } - if (session == null) { - Log.w(TAG, "Fail to create another session"); - mPipShowing = false; - return; - } - runOnUiThread(new Runnable() { - @Override - public void run() { - mPipSession = session; - mPipSession.setVolume(0); - mPipSession.tune(mChannelMap.getCurrentChannelUri()); - mPipView.setVisibility(View.VISIBLE); - } - }); - } - - @Override - public void onSessionReleased(final TvInputManager.Session session) { - Log.w(TAG, "PIP session is released by crash"); - if (mPipSession == session) { - stopPipSession(); - } - } - }; + private void stopPip() { + if (DEBUG) Log.d(TAG, "stopPip"); + if (mPipView.isPlaying()) { + mPipView.setVisibility(View.INVISIBLE); + mPipView.stop(); + } + mPipShowing = false; + } private final Runnable mOnChannelsLoadFinished = new Runnable() { @Override @@ -680,39 +606,37 @@ public class TvActivity extends Activity implements mTunePendding = true; return; } - Uri currentChannelUri = mChannelMap.getCurrentChannelUri(); - if (currentChannelUri == null) { - stopSession(); - mTunePendding = false; + mTunePendding = false; + long channelId = mChannelMap.getCurrentChannelId(); + if (channelId == Channel.INVALID_ID) { + stopTv(); Toast.makeText(this, R.string.input_is_not_available, Toast.LENGTH_SHORT).show(); return; } - String inputId = Utils.getInputIdForChannel(this, currentChannelUri); - if (!mTvInputInfo.getId().equals(inputId)) { - if (DEBUG) Log.d(TAG, "TV input is changed"); - changeSession(mTvInputManagerHelper.getTvInputInfo(inputId)); - mTunePendding = true; - return; - } - if (mTvSession == null) { - if (DEBUG) Log.d(TAG, "Session is not created yet"); - mTunePendding = true; - return; - } - setVolumeByAudioFocusStatus(); - if (currentChannelUri != null) { - // TODO: implement 'no signal' - // TODO: add result callback and show a message on failure. - Utils.setLastWatchedChannel(this, mTvInputInfo.getId(), currentChannelUri); - mTvSession.tune(currentChannelUri); - if (isShyModeSet()) { - setShynessMode(false); - // TODO: Set the shy mode to true when tune() fails. + mTvView.tuneTo(channelId, new OnTuneListener() { + @Override + public void onUnexpectedStop(long channelId) { + stopTv(); + startTv(Channel.INVALID_ID); + } + + @Override + public void onTuned(boolean success, long channelId) { + if (!success) { + Log.w(TAG, "Failed to tune to channel " + channelId); + // TODO: show something to user about this error. + } else { + Utils.setLastWatchedChannelId(TvActivity.this, + mTvView.getCurrentTvInputInfo().getId(), channelId); + } } - displayChannelBanner(); + }); + displayChannelBanner(); + if (isShyModeSet()) { + setShynessMode(false); + // TODO: Set the shy mode to true when tune() fails. } - mTunePendding = false; } private void displayChannelBanner() { @@ -747,34 +671,6 @@ public class TvActivity extends Activity implements new RecentlyWatchedDialogFragment()); } - private void stopSession() { - if (mTvInputInfo != null) { - if (mTvSession != null) { - mAudioManager.abandonAudioFocus(this); - mTvSession = null; - } - mTvView.unbindTvInput(); - mTvInputInfo = null; - } - if (mChannelMap != null) { - mMainMenuView.setChannelMap(null); - mChannelMap.close(); - mChannelMap = null; - } - mTunePendding = false; - } - - private void stopPipSession() { - if (DEBUG) Log.d(TAG, "stopPipSession"); - if (mPipSession != null) { - mPipView.setVisibility(View.INVISIBLE); - mPipView.unbindTvInput(); - mPipSession = null; - mPipInputInfo = null; - } - mPipShowing = false; - } - @Override protected void onSaveInstanceState(Bundle outState) { // Do not save instance state because restoring instance state when TV app died @@ -784,8 +680,6 @@ public class TvActivity extends Activity implements @Override protected void onDestroy() { - mTvView.getHolder().removeCallback(mSurfaceHolderCallback); - mPipView.getHolder().removeCallback(mSurfaceHolderCallback); PreferenceManager.getDefaultSharedPreferences(this).edit() .putBoolean(PREF_KEY_IS_UNIFIED_TV_INPUT, mIsUnifiedTvInput).apply(); if (DEBUG) Log.d(TAG, "onDestroy()"); @@ -802,8 +696,8 @@ public class TvActivity extends Activity implements return super.onKeyUp(keyCode, event); } - if (mHandler.hasMessages(MSG_START_DEFAULT_SESSION_RETRY)) { - // Ignore key events during startDefaultSession retry. + if (mHandler.hasMessages(MSG_START_TV_RETRY)) { + // Ignore key events during startTv retry. return true; } if (mChannelMap == null) { @@ -913,9 +807,9 @@ public class TvActivity extends Activity implements public void togglePipView() { if (mPipShowing) { - stopPipSession(); + stopPip(); } else { - startPipSession(); + startPip(); } } diff --git a/src/com/android/tv/TvInputManagerHelper.java b/src/com/android/tv/TvInputManagerHelper.java index 33c6b15e..e586395a 100644 --- a/src/com/android/tv/TvInputManagerHelper.java +++ b/src/com/android/tv/TvInputManagerHelper.java @@ -136,6 +136,9 @@ public class TvInputManagerHelper { if (!mStarted) { throw new IllegalStateException("AvailabilityManager doesn't started"); } + if (inputId == null) { + return null; + } TvInputInfo input = mInputMap.get(inputId); if (input == null) { update(); @@ -148,6 +151,10 @@ public class TvInputManagerHelper { return mInputAvailabilityMap.size(); } + public boolean isAvailable(TvInputInfo inputInfo) { + return isAvailable(inputInfo.getId()); + } + public boolean isAvailable(String inputId) { if (!mStarted) { throw new IllegalStateException("AvailabilityManager doesn't started"); diff --git a/src/com/android/tv/Utils.java b/src/com/android/tv/Utils.java index 250553c3..b4b7b37e 100644 --- a/src/com/android/tv/Utils.java +++ b/src/com/android/tv/Utils.java @@ -59,13 +59,17 @@ public class Utils { private static final String PREFIX_PREF_NAME = "com.android.tv."; // preferences stored in the preference of a specific tv input. - private static final String PREF_KEY_LAST_WATCHED_CHANNEL = "last_watched_channel"; + private static final String PREF_KEY_LAST_WATCHED_CHANNEL_ID = "last_watched_channel_id"; // TODO: Remove this and add inputId into TvProvider. public static String getInputIdForComponentName(ComponentName name) { return name.flattenToShortString(); } + public static Uri getChannelUri(long channelId) { + return ContentUris.withAppendedId(TvContract.Channels.CONTENT_URI, channelId); + } + public static String getInputIdForChannel(Context context, long channelId) { if (channelId == Channel.INVALID_ID) { return null; @@ -95,40 +99,31 @@ public class Utils { return getInputIdForComponentName(componentName); } - public static void setLastWatchedChannel(Context context, String inputId, Uri channelUri) { + public static void setLastWatchedChannelId(Context context, String inputId, long channelId) { if (TextUtils.isEmpty(inputId)) { throw new IllegalArgumentException("inputId cannot be empty"); } context.getSharedPreferences(getPreferenceName(inputId), Context.MODE_PRIVATE).edit() - .putString(PREF_KEY_LAST_WATCHED_CHANNEL, channelUri.toString()).apply(); + .putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, channelId).apply(); PreferenceManager.getDefaultSharedPreferences(context).edit() .putString(PREF_KEY_LAST_SELECTED_TV_INPUT, inputId).apply(); } - public static Uri getLastWatchedChannel(Context context) { + public static long getLastWatchedChannelId(Context context) { String inputId = PreferenceManager.getDefaultSharedPreferences(context) .getString(PREF_KEY_LAST_SELECTED_TV_INPUT, null); if (inputId == null) { - return null; + return Channel.INVALID_ID; } - return getLastWatchedChannel(context, inputId); - } - - public static long getLastWatchedChannelId(Context context) { - return getChannelId(getLastWatchedChannel(context)); + return getLastWatchedChannelId(context, inputId); } - public static Uri getLastWatchedChannel(Context context, String inputId) { + public static long getLastWatchedChannelId(Context context, String inputId) { if (TextUtils.isEmpty(inputId)) { throw new IllegalArgumentException("inputId cannot be empty"); } - String channel = context.getSharedPreferences(getPreferenceName(inputId), - Context.MODE_PRIVATE).getString(PREF_KEY_LAST_WATCHED_CHANNEL, null); - return channel == null ? null : Uri.parse(channel); - } - - public static long getLastWatchedChannelId(Context context, String inputId) { - return getChannelId(getLastWatchedChannel(context, inputId)); + return context.getSharedPreferences(getPreferenceName(inputId), + Context.MODE_PRIVATE).getLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, Channel.INVALID_ID); } public static Program getCurrentProgram(Context context, Uri channelUri) { |