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