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