TunableTvView.java revision 4e5a6a47adb40039a27536236656204f9ad12df3
1package com.android.tv.ui; 2 3import android.content.Context; 4import android.content.pm.PackageManager; 5import android.media.tv.TvInputInfo; 6import android.media.tv.TvInputManager; 7import android.media.tv.TvTrackInfo; 8import android.media.tv.TvView; 9import android.net.Uri; 10import android.util.AttributeSet; 11import android.util.Log; 12import android.view.KeyEvent; 13import android.view.MotionEvent; 14import android.view.SurfaceHolder; 15import android.view.SurfaceView; 16 17import com.android.tv.R; 18import com.android.tv.data.Channel; 19import com.android.tv.data.StreamInfo; 20import com.android.tv.util.TvInputManagerHelper; 21import com.android.tv.util.Utils; 22 23import java.util.List; 24 25public class TunableTvView extends TvView implements StreamInfo { 26 private static final boolean DEBUG = true; 27 private static final String TAG = "TunableTvView"; 28 29 private static final int DELAY_FOR_SURFACE_RELEASE = 300; 30 public static final String PERMISSION_RECEIVE_INPUT_EVENT = 31 "android.permission.RECEIVE_INPUT_EVENT"; 32 33 private long mChannelId = Channel.INVALID_ID; 34 private TvInputManagerHelper mInputManagerHelper; 35 private boolean mStarted; 36 private TvInputInfo mInputInfo; 37 private OnTuneListener mOnTuneListener; 38 private int mVideoWidth; 39 private int mVideoHeight; 40 private int mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; 41 private int mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; 42 private boolean mHasClosedCaption = false; 43 private boolean mIsVideoAvailable; 44 private int mVideoUnavailableReason; 45 private SurfaceView mSurface; 46 private boolean mCanReceiveInputEvent; 47 48 private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { 49 @Override 50 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } 51 52 @Override 53 public void surfaceCreated(SurfaceHolder holder) { } 54 55 @Override 56 public void surfaceDestroyed(SurfaceHolder holder) { 57 // TODO: It is a hack to wait to release a surface at TIS. If there is a way to 58 // know when the surface is released at TIS, we don't need this hack. 59 try { 60 if (DEBUG) Log.d(TAG, "Sleep to wait destroying a surface"); 61 Thread.sleep(DELAY_FOR_SURFACE_RELEASE); 62 if (DEBUG) Log.d(TAG, "Wake up from sleeping"); 63 } catch (InterruptedException e) { 64 e.printStackTrace(); 65 } 66 } 67 }; 68 69 private final TvInputListener mListener = 70 new TvInputListener() { 71 @Override 72 public void onError(String inputId, int errorCode) { 73 if (errorCode == TvView.ERROR_BUSY) { 74 Log.w(TAG, "Failed to bind an input"); 75 long channelId = mChannelId; 76 mChannelId = Channel.INVALID_ID; 77 mInputInfo = null; 78 mCanReceiveInputEvent = false; 79 if (mOnTuneListener != null) { 80 mOnTuneListener.onTuned(false, channelId); 81 mOnTuneListener = null; 82 } 83 } else if (errorCode == TvView.ERROR_TV_INPUT_DISCONNECTED) { 84 Log.w(TAG, "Session is released by crash"); 85 long channelId = mChannelId; 86 mChannelId = Channel.INVALID_ID; 87 mInputInfo = null; 88 mCanReceiveInputEvent = false; 89 if (mOnTuneListener != null) { 90 mOnTuneListener.onUnexpectedStop(channelId); 91 mOnTuneListener = null; 92 } 93 } 94 } 95 96 @Override 97 public void onChannelRetuned(String inputId, Uri channelUri) { 98 if (DEBUG) { 99 Log.d(TAG, "onChannelRetuned(inputId=" + inputId + ", channelUri=" 100 + channelUri + ")"); 101 } 102 // TODO: update {@code mChannelId}. 103 if (mOnTuneListener != null) { 104 mOnTuneListener.onChannelChanged(channelUri); 105 } 106 } 107 108 @Override 109 public void onTrackInfoChanged(String inputId, List<TvTrackInfo> tracks) { 110 for (TvTrackInfo track : tracks) { 111 int type = track.getInt(TvTrackInfo.KEY_TYPE); 112 boolean selected = track.getBoolean(TvTrackInfo.KEY_IS_SELECTED); 113 if (type == TvTrackInfo.VALUE_TYPE_VIDEO && selected) { 114 mVideoWidth = track.getInt(TvTrackInfo.KEY_WIDTH); 115 mVideoHeight = track.getInt(TvTrackInfo.KEY_HEIGHT); 116 mVideoFormat = Utils.getVideoDefinitionLevelFromSize( 117 mVideoWidth, mVideoHeight); 118 } else if (type == TvTrackInfo.VALUE_TYPE_AUDIO && selected) { 119 mAudioChannelCount = track.getInt(TvTrackInfo.KEY_CHANNEL_COUNT); 120 } else if (type == TvTrackInfo.VALUE_TYPE_SUBTITLE) { 121 mHasClosedCaption = true; 122 } 123 } 124 if (mOnTuneListener != null) { 125 mOnTuneListener.onStreamInfoChanged(TunableTvView.this); 126 } 127 } 128 129 @Override 130 public void onVideoAvailable(String inputId) { 131 mIsVideoAvailable = true; 132 if (mOnTuneListener != null) { 133 mOnTuneListener.onStreamInfoChanged(TunableTvView.this); 134 } 135 } 136 137 @Override 138 public void onVideoUnavailable(String inputId, int reason) { 139 mIsVideoAvailable = false; 140 mVideoUnavailableReason = reason; 141 if (mOnTuneListener != null) { 142 mOnTuneListener.onStreamInfoChanged(TunableTvView.this); 143 } 144 } 145 }; 146 147 public TunableTvView(Context context) { 148 this(context, null, 0); 149 } 150 151 public TunableTvView(Context context, AttributeSet attrs) { 152 this(context, attrs, 0); 153 } 154 155 public TunableTvView(Context context, AttributeSet attrs, int defStyleAttr) { 156 super(context, attrs, defStyleAttr); 157 for (int i = 0; i < getChildCount(); ++i) { 158 if (getChildAt(i) instanceof SurfaceView) { 159 mSurface = (SurfaceView) getChildAt(i); 160 mSurface.getHolder().addCallback(mSurfaceHolderCallback); 161 return; 162 } 163 } 164 throw new RuntimeException("TvView does not have SurfaceView."); 165 } 166 167 public void start(TvInputManagerHelper tvInputManagerHelper) { 168 mInputManagerHelper = tvInputManagerHelper; 169 if (mStarted) { 170 return; 171 } 172 mStarted = true; 173 } 174 175 public void stop() { 176 if (!mStarted) { 177 return; 178 } 179 mStarted = false; 180 reset(); 181 mChannelId = Channel.INVALID_ID; 182 mInputInfo = null; 183 mCanReceiveInputEvent = false; 184 mOnTuneListener = null; 185 } 186 187 public boolean isPlaying() { 188 return mStarted; 189 } 190 191 public boolean tuneTo(long channelId, OnTuneListener listener) { 192 if (!mStarted) { 193 throw new IllegalStateException("TvView isn't started"); 194 } 195 if (DEBUG) Log.d(TAG, "tuneTo " + channelId); 196 mVideoWidth = 0; 197 mVideoHeight = 0; 198 mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; 199 mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; 200 mHasClosedCaption = false; 201 String inputId = Utils.getInputIdForChannel(getContext(), channelId); 202 TvInputInfo inputInfo = mInputManagerHelper.getTvInputInfo(inputId); 203 if (inputInfo == null 204 || mInputManagerHelper.getInputState(inputInfo) == 205 TvInputManager.INPUT_STATE_DISCONNECTED) { 206 return false; 207 } 208 mOnTuneListener = listener; 209 mChannelId = channelId; 210 if (!inputInfo.equals(mInputInfo)) { 211 reset(); 212 // TODO: It is a hack to wait to release a surface at TIS. If there is a way to 213 // know when the surface is released at TIS, we don't need this hack. 214 try { 215 Thread.sleep(DELAY_FOR_SURFACE_RELEASE); 216 } catch (InterruptedException e) { 217 e.printStackTrace(); 218 } 219 mInputInfo = inputInfo; 220 mCanReceiveInputEvent = mContext.getPackageManager().checkPermission( 221 PERMISSION_RECEIVE_INPUT_EVENT, mInputInfo.getComponent().getPackageName()) 222 == PackageManager.PERMISSION_GRANTED; 223 } 224 setTvInputListener(mListener); 225 tune(mInputInfo.getId(), Utils.getChannelUri(mChannelId)); 226 if (mOnTuneListener != null) { 227 // TODO: Add a callback for tune complete and call onTuned when it was successful. 228 mOnTuneListener.onTuned(true, mChannelId); 229 } 230 return true; 231 } 232 233 @Override 234 public TvInputInfo getCurrentTvInputInfo() { 235 return mInputInfo; 236 } 237 238 public long getCurrentChannelId() { 239 return mChannelId; 240 } 241 242 public void setPip(boolean isPip) { 243 mSurface.setZOrderMediaOverlay(isPip); 244 } 245 246 @Override 247 public void setStreamVolume(float volume) { 248 if (!mStarted) { 249 throw new IllegalStateException("TvView isn't started"); 250 } 251 if (DEBUG) 252 Log.d(TAG, "setStreamVolume " + volume); 253 super.setStreamVolume(volume); 254 } 255 256 @Override 257 public boolean dispatchKeyEvent(KeyEvent event) { 258 return mCanReceiveInputEvent && super.dispatchKeyEvent(event); 259 } 260 261 @Override 262 public boolean dispatchTouchEvent(MotionEvent event) { 263 return mCanReceiveInputEvent && super.dispatchTouchEvent(event); 264 } 265 266 @Override 267 public boolean dispatchTrackballEvent(MotionEvent event) { 268 return mCanReceiveInputEvent && super.dispatchTrackballEvent(event); 269 } 270 271 @Override 272 public boolean dispatchGenericMotionEvent(MotionEvent event) { 273 return mCanReceiveInputEvent && super.dispatchGenericMotionEvent(event); 274 } 275 276 public interface OnTuneListener { 277 void onTuned(boolean success, long channelId); 278 void onUnexpectedStop(long channelId); 279 void onStreamInfoChanged(StreamInfo info); 280 void onChannelChanged(Uri channel); 281 } 282 283 @Override 284 public int getVideoWidth() { 285 return mVideoWidth; 286 } 287 288 @Override 289 public int getVideoHeight() { 290 return mVideoHeight; 291 } 292 293 @Override 294 public int getVideoDefinitionLevel() { 295 return mVideoFormat; 296 } 297 298 @Override 299 public int getAudioChannelCount() { 300 return mAudioChannelCount; 301 } 302 303 @Override 304 public boolean hasClosedCaption() { 305 return mHasClosedCaption; 306 } 307 308 @Override 309 public boolean isVideoAvailable() { 310 return mIsVideoAvailable; 311 } 312 313 @Override 314 public int getVideoUnavailableReason() { 315 return mVideoUnavailableReason; 316 } 317} 318