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