1/* 2 * Copyright (C) 2015 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.usbtuner.tvinput; 18 19import android.annotation.TargetApi; 20import android.content.Context; 21import android.media.PlaybackParams; 22import android.media.tv.TvContentRating; 23import android.media.tv.TvInputManager; 24import android.media.tv.TvInputService; 25import android.net.Uri; 26import android.os.Build; 27import android.os.Handler; 28import android.os.Message; 29import android.os.SystemClock; 30import android.text.Html; 31import android.util.Log; 32import android.view.LayoutInflater; 33import android.view.Surface; 34import android.view.View; 35import android.view.ViewGroup; 36import android.widget.TextView; 37import android.widget.Toast; 38 39import com.google.android.exoplayer.audio.AudioCapabilities; 40import com.android.usbtuner.R; 41import com.android.usbtuner.cc.CaptionLayout; 42import com.android.usbtuner.cc.CaptionTrackRenderer; 43import com.android.usbtuner.data.Cea708Data.CaptionEvent; 44import com.android.usbtuner.data.Track.AtscCaptionTrack; 45import com.android.usbtuner.data.TunerChannel; 46import com.android.usbtuner.exoplayer.cache.CacheManager; 47import com.android.usbtuner.util.StatusTextUtils; 48import com.android.usbtuner.util.SystemPropertiesProxy; 49 50/** 51 * Provides a USB tuner TV input session. It handles Overlay UI works. Main tuner input functions 52 * are implemented in {@link TunerSessionWorker}. 53 */ 54public class TunerSession extends TvInputService.Session implements Handler.Callback { 55 private static final String TAG = "TunerSession"; 56 private static final boolean DEBUG = false; 57 private static final String USBTUNER_SHOW_DEBUG = "persist.usbtuner.show_debug"; 58 59 public static final int MSG_UI_SHOW_MESSAGE = 1; 60 public static final int MSG_UI_HIDE_MESSAGE = 2; 61 public static final int MSG_UI_SHOW_AUDIO_UNPLAYABLE = 3; 62 public static final int MSG_UI_HIDE_AUDIO_UNPLAYABLE = 4; 63 public static final int MSG_UI_PROCESS_CAPTION_TRACK = 5; 64 public static final int MSG_UI_START_CAPTION_TRACK = 6; 65 public static final int MSG_UI_STOP_CAPTION_TRACK = 7; 66 public static final int MSG_UI_RESET_CAPTION_TRACK = 8; 67 public static final int MSG_UI_SET_STATUS_TEXT = 9; 68 public static final int MSG_UI_TOAST_RESCAN_NEEDED = 10; 69 70 private final Context mContext; 71 private final Handler mUiHandler; 72 private final View mOverlayView; 73 private final TextView mMessageView; 74 private final TextView mStatusView; 75 private final TextView mAudioStatusView; 76 private final ViewGroup mMessageLayout; 77 private final CaptionTrackRenderer mCaptionTrackRenderer; 78 private final TunerSessionWorker mSessionWorker; 79 private boolean mReleased = false; 80 private boolean mVideoAvailable = false; 81 private boolean mPlayPaused; 82 private long mTuneStartTimestamp; 83 84 public TunerSession(Context context, ChannelDataManager channelDataManager, 85 CacheManager cacheManager) { 86 super(context); 87 mContext = context; 88 mUiHandler = new Handler(this); 89 LayoutInflater inflater = (LayoutInflater) 90 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 91 mOverlayView = inflater.inflate(R.layout.ut_overlay_view, null); 92 mMessageLayout = (ViewGroup) mOverlayView.findViewById(R.id.message_layout); 93 mMessageLayout.setVisibility(View.INVISIBLE); 94 mMessageView = (TextView) mOverlayView.findViewById(R.id.message); 95 mStatusView = (TextView) mOverlayView.findViewById(R.id.tuner_status); 96 boolean showDebug = SystemPropertiesProxy.getBoolean(USBTUNER_SHOW_DEBUG, false); 97 mStatusView.setVisibility(showDebug ? View.VISIBLE : View.INVISIBLE); 98 mAudioStatusView = (TextView) mOverlayView.findViewById(R.id.audio_status); 99 mAudioStatusView.setVisibility(View.INVISIBLE); 100 mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML( 101 context.getString(R.string.ut_ac3_passthrough_unavailable)))); 102 CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption); 103 mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout); 104 mSessionWorker = new TunerSessionWorker(context, channelDataManager, 105 cacheManager, this); 106 } 107 108 public boolean isReleased() { 109 return mReleased; 110 } 111 112 @Override 113 public View onCreateOverlayView() { 114 return mOverlayView; 115 } 116 117 @Override 118 public boolean onSelectTrack(int type, String trackId) { 119 mSessionWorker.sendMessage( 120 TunerSessionWorker.MSG_SELECT_TRACK, type, 0, trackId); 121 return false; 122 } 123 124 @Override 125 public void onSetCaptionEnabled(boolean enabled) { 126 mSessionWorker.sendMessage( 127 TunerSessionWorker.MSG_SET_CAPTION_ENABLED, enabled); 128 } 129 130 @Override 131 public void onSetStreamVolume(float volume) { 132 mSessionWorker.sendMessage(TunerSessionWorker.MSG_SET_STREAM_VOLUME, volume); 133 } 134 135 @Override 136 public boolean onSetSurface(Surface surface) { 137 mSessionWorker.sendMessage(TunerSessionWorker.MSG_SET_SURFACE, surface); 138 return true; 139 } 140 141 @Override 142 public void onTimeShiftPause() { 143 mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_PAUSE); 144 mPlayPaused = true; 145 } 146 147 @Override 148 public void onTimeShiftResume() { 149 mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_RESUME); 150 mPlayPaused = false; 151 } 152 153 @Override 154 public void onTimeShiftSeekTo(long timeMs) { 155 if (DEBUG) Log.d(TAG, "Timeshift seekTo requested position: " + timeMs / 1000); 156 mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_SEEK_TO, 157 mPlayPaused ? 1 : 0, 0, timeMs); 158 } 159 160 @Override 161 public void onTimeShiftSetPlaybackParams(PlaybackParams params) { 162 mSessionWorker.sendMessage( 163 TunerSessionWorker.MSG_TIMESHIFT_SET_PLAYBACKPARAMS, params); 164 } 165 166 @Override 167 public long onTimeShiftGetStartPosition() { 168 return mSessionWorker.getStartPosition(); 169 } 170 171 @Override 172 public long onTimeShiftGetCurrentPosition() { 173 return mSessionWorker.getCurrentPosition(); 174 } 175 176 @Override 177 public boolean onTune(Uri channelUri) { 178 if (DEBUG) { 179 Log.d(TAG, "onTune to " + channelUri != null ? channelUri.toString() : ""); 180 } 181 if (channelUri == null) { 182 Log.w(TAG, "onTune() is failed due to null channelUri."); 183 mSessionWorker.stopTune(); 184 return false; 185 } 186 mTuneStartTimestamp = SystemClock.elapsedRealtime(); 187 mSessionWorker.tune(channelUri); 188 mPlayPaused = false; 189 return true; 190 } 191 192 @TargetApi(Build.VERSION_CODES.N) 193 @Override 194 public void onTimeShiftPlay(Uri recordUri) { 195 if (recordUri == null) { 196 Log.w(TAG, "onTimeShiftPlay() is failed due to null channelUri."); 197 mSessionWorker.stopTune(); 198 return; 199 } 200 mTuneStartTimestamp = SystemClock.elapsedRealtime(); 201 mSessionWorker.tune(recordUri); 202 mPlayPaused = false; 203 } 204 205 @Override 206 public void onUnblockContent(TvContentRating unblockedRating) { 207 mSessionWorker.sendMessage(TunerSessionWorker.MSG_UNBLOCKED_RATING, 208 unblockedRating); 209 } 210 211 @Override 212 public void onRelease() { 213 if (DEBUG) { 214 Log.d(TAG, "onRelease"); 215 } 216 mReleased = true; 217 mSessionWorker.release(); 218 mUiHandler.removeCallbacksAndMessages(null); 219 } 220 221 /** 222 * Sets {@link AudioCapabilities}. 223 */ 224 public void setAudioCapabilities(AudioCapabilities audioCapabilities) { 225 mSessionWorker.sendMessage(TunerSessionWorker.MSG_AUDIO_CAPABILITIES_CHANGED, 226 audioCapabilities); 227 } 228 229 @Override 230 public void notifyVideoAvailable() { 231 super.notifyVideoAvailable(); 232 if (mTuneStartTimestamp != 0) { 233 Log.i(TAG, "[Profiler] Video available in " 234 + (SystemClock.elapsedRealtime() - mTuneStartTimestamp) + " ms"); 235 mTuneStartTimestamp = 0; 236 } 237 mVideoAvailable = true; 238 } 239 240 @Override 241 public void notifyVideoUnavailable(int reason) { 242 switch (reason) { 243 case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING: 244 case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: 245 case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY: 246 case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: 247 super.notifyVideoUnavailable(reason); 248 break; 249 case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: 250 default: 251 super.notifyVideoAvailable(); 252 TunerChannel channel = mSessionWorker.getCurrentChannel(); 253 if (channel != null) { 254 sendUiMessage(TunerSession.MSG_UI_SHOW_MESSAGE, 255 mContext.getString(R.string.ut_fail_to_tune, channel.getName())); 256 } else { 257 sendUiMessage(TunerSession.MSG_UI_SHOW_MESSAGE, 258 mContext.getString(R.string.ut_fail_to_tune_to_unknown_channel)); 259 } 260 break; 261 } 262 if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING 263 && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL) { 264 notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE); 265 } 266 } 267 268 public void sendUiMessage(int message) { 269 mUiHandler.sendEmptyMessage(message); 270 } 271 272 public void sendUiMessage(int message, Object object) { 273 mUiHandler.obtainMessage(message, object).sendToTarget(); 274 } 275 276 public void sendUiMessage(int message, int arg1, int arg2, Object object) { 277 mUiHandler.obtainMessage(message, arg1, arg2, object).sendToTarget(); 278 } 279 280 @Override 281 public boolean handleMessage(Message msg) { 282 switch (msg.what) { 283 case MSG_UI_SHOW_MESSAGE: { 284 mMessageView.setText((String) msg.obj); 285 mMessageLayout.setVisibility(View.VISIBLE); 286 return true; 287 } 288 case MSG_UI_HIDE_MESSAGE: { 289 mMessageLayout.setVisibility(View.INVISIBLE); 290 return true; 291 } 292 case MSG_UI_SHOW_AUDIO_UNPLAYABLE: { 293 mAudioStatusView.setVisibility(View.VISIBLE); 294 return true; 295 } 296 case MSG_UI_HIDE_AUDIO_UNPLAYABLE: { 297 mAudioStatusView.setVisibility(View.INVISIBLE); 298 return true; 299 } 300 case MSG_UI_PROCESS_CAPTION_TRACK: { 301 mCaptionTrackRenderer.processCaptionEvent((CaptionEvent) msg.obj); 302 return true; 303 } 304 case MSG_UI_START_CAPTION_TRACK: { 305 mCaptionTrackRenderer.start((AtscCaptionTrack) msg.obj); 306 return true; 307 } 308 case MSG_UI_STOP_CAPTION_TRACK: { 309 mCaptionTrackRenderer.stop(); 310 return true; 311 } 312 case MSG_UI_RESET_CAPTION_TRACK: { 313 mCaptionTrackRenderer.reset(); 314 return true; 315 } 316 case MSG_UI_SET_STATUS_TEXT: { 317 mStatusView.setText((CharSequence) msg.obj); 318 return true; 319 } 320 case MSG_UI_TOAST_RESCAN_NEEDED: { 321 Toast.makeText(mContext, R.string.ut_rescan_needed, Toast.LENGTH_LONG).show(); 322 return true; 323 } 324 } 325 return false; 326 } 327} 328