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.cc.CaptionLayout; 42 import com.android.tv.tuner.cc.CaptionTrackRenderer; 43 import com.android.tv.tuner.data.Cea708Data.CaptionEvent; 44 import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; 45 import com.android.tv.tuner.exoplayer.buffer.BufferManager; 46 import com.android.tv.tuner.data.TunerChannel; 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 Handler.Callback { 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_SET_STATUS_TEXT = 9; 69 public static final int MSG_UI_TOAST_RESCAN_NEEDED = 10; 70 71 private final Context mContext; 72 private final Handler mUiHandler; 73 private final View mOverlayView; 74 private final TextView mMessageView; 75 private final TextView mStatusView; 76 private final TextView mAudioStatusView; 77 private final ViewGroup mMessageLayout; 78 private final CaptionTrackRenderer mCaptionTrackRenderer; 79 private final TunerSessionWorker mSessionWorker; 80 private boolean mReleased = false; 81 private boolean mPlayPaused; 82 private long mTuneStartTimestamp; 83 TunerSession(Context context, ChannelDataManager channelDataManager, BufferManager bufferManager)84 public TunerSession(Context context, ChannelDataManager channelDataManager, 85 BufferManager bufferManager) { 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_surround_sound_disabled)))); 102 CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption); 103 mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout); 104 mSessionWorker = new TunerSessionWorker(context, channelDataManager, 105 bufferManager, this); 106 } 107 isReleased()108 public boolean isReleased() { 109 return mReleased; 110 } 111 112 @Override onCreateOverlayView()113 public View onCreateOverlayView() { 114 return mOverlayView; 115 } 116 117 @Override onSelectTrack(int type, String trackId)118 public boolean onSelectTrack(int type, String trackId) { 119 mSessionWorker.sendMessage(TunerSessionWorker.MSG_SELECT_TRACK, type, 0, trackId); 120 return false; 121 } 122 123 @Override onSetCaptionEnabled(boolean enabled)124 public void onSetCaptionEnabled(boolean enabled) { 125 mSessionWorker.setCaptionEnabled(enabled); 126 } 127 128 @Override onSetStreamVolume(float volume)129 public void onSetStreamVolume(float volume) { 130 mSessionWorker.setStreamVolume(volume); 131 } 132 133 @Override onSetSurface(Surface surface)134 public boolean onSetSurface(Surface surface) { 135 mSessionWorker.setSurface(surface); 136 return true; 137 } 138 139 @Override onTimeShiftPause()140 public void onTimeShiftPause() { 141 mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_PAUSE); 142 mPlayPaused = true; 143 } 144 145 @Override onTimeShiftResume()146 public void onTimeShiftResume() { 147 mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_RESUME); 148 mPlayPaused = false; 149 } 150 151 @Override onTimeShiftSeekTo(long timeMs)152 public void onTimeShiftSeekTo(long timeMs) { 153 if (DEBUG) Log.d(TAG, "Timeshift seekTo requested position: " + timeMs / 1000); 154 mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_SEEK_TO, 155 mPlayPaused ? 1 : 0, 0, timeMs); 156 } 157 158 @Override onTimeShiftSetPlaybackParams(PlaybackParams params)159 public void onTimeShiftSetPlaybackParams(PlaybackParams params) { 160 mSessionWorker.sendMessage( 161 TunerSessionWorker.MSG_TIMESHIFT_SET_PLAYBACKPARAMS, params); 162 } 163 164 @Override onTimeShiftGetStartPosition()165 public long onTimeShiftGetStartPosition() { 166 return mSessionWorker.getStartPosition(); 167 } 168 169 @Override onTimeShiftGetCurrentPosition()170 public long onTimeShiftGetCurrentPosition() { 171 return mSessionWorker.getCurrentPosition(); 172 } 173 174 @Override onTune(Uri channelUri)175 public boolean onTune(Uri channelUri) { 176 if (DEBUG) { 177 Log.d(TAG, "onTune to " + channelUri != null ? channelUri.toString() : ""); 178 } 179 if (channelUri == null) { 180 Log.w(TAG, "onTune() is failed due to null channelUri."); 181 mSessionWorker.stopTune(); 182 return false; 183 } 184 mTuneStartTimestamp = SystemClock.elapsedRealtime(); 185 mSessionWorker.tune(channelUri); 186 mPlayPaused = false; 187 return true; 188 } 189 190 @TargetApi(Build.VERSION_CODES.N) 191 @Override onTimeShiftPlay(Uri recordUri)192 public void onTimeShiftPlay(Uri recordUri) { 193 if (recordUri == null) { 194 Log.w(TAG, "onTimeShiftPlay() is failed due to null channelUri."); 195 mSessionWorker.stopTune(); 196 return; 197 } 198 mTuneStartTimestamp = SystemClock.elapsedRealtime(); 199 mSessionWorker.tune(recordUri); 200 mPlayPaused = false; 201 } 202 203 @Override onUnblockContent(TvContentRating unblockedRating)204 public void onUnblockContent(TvContentRating unblockedRating) { 205 mSessionWorker.sendMessage(TunerSessionWorker.MSG_UNBLOCKED_RATING, 206 unblockedRating); 207 } 208 209 @Override onRelease()210 public void onRelease() { 211 if (DEBUG) { 212 Log.d(TAG, "onRelease"); 213 } 214 mReleased = true; 215 mSessionWorker.release(); 216 mUiHandler.removeCallbacksAndMessages(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.setVisibility(View.VISIBLE); 276 } else { 277 Log.e(TAG, "Audio is unavailable, surround sound setting is " + value); 278 } 279 return true; 280 } 281 case MSG_UI_HIDE_AUDIO_UNPLAYABLE: { 282 mAudioStatusView.setVisibility(View.INVISIBLE); 283 return true; 284 } 285 case MSG_UI_PROCESS_CAPTION_TRACK: { 286 mCaptionTrackRenderer.processCaptionEvent((CaptionEvent) msg.obj); 287 return true; 288 } 289 case MSG_UI_START_CAPTION_TRACK: { 290 mCaptionTrackRenderer.start((AtscCaptionTrack) msg.obj); 291 return true; 292 } 293 case MSG_UI_STOP_CAPTION_TRACK: { 294 mCaptionTrackRenderer.stop(); 295 return true; 296 } 297 case MSG_UI_RESET_CAPTION_TRACK: { 298 mCaptionTrackRenderer.reset(); 299 return true; 300 } 301 case MSG_UI_SET_STATUS_TEXT: { 302 mStatusView.setText((CharSequence) msg.obj); 303 return true; 304 } 305 case MSG_UI_TOAST_RESCAN_NEEDED: { 306 Toast.makeText(mContext, R.string.ut_rescan_needed, Toast.LENGTH_LONG).show(); 307 return true; 308 } 309 } 310 return false; 311 } 312 } 313