blob: 44bae90827919ac3cf4314bc0fe88fd431210cfe [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tv.tuner.tvinput;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.PlaybackParams;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputService;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.exoplayer.audio.AudioCapabilities;
import com.android.tv.tuner.R;
import com.android.tv.tuner.TunerPreferences;
import com.android.tv.tuner.TunerPreferences.TunerPreferencesChangedListener;
import com.android.tv.tuner.cc.CaptionLayout;
import com.android.tv.tuner.cc.CaptionTrackRenderer;
import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import com.android.tv.tuner.util.GlobalSettingsUtils;
import com.android.tv.tuner.util.StatusTextUtils;
import com.android.tv.tuner.util.SystemPropertiesProxy;
/**
* Provides a tuner TV input session. It handles Overlay UI works. Main tuner input functions
* are implemented in {@link TunerSessionWorker}.
*/
public class TunerSession extends TvInputService.Session implements
Handler.Callback, TunerPreferencesChangedListener {
private static final String TAG = "TunerSession";
private static final boolean DEBUG = false;
private static final String USBTUNER_SHOW_DEBUG = "persist.tv.tuner.show_debug";
public static final int MSG_UI_SHOW_MESSAGE = 1;
public static final int MSG_UI_HIDE_MESSAGE = 2;
public static final int MSG_UI_SHOW_AUDIO_UNPLAYABLE = 3;
public static final int MSG_UI_HIDE_AUDIO_UNPLAYABLE = 4;
public static final int MSG_UI_PROCESS_CAPTION_TRACK = 5;
public static final int MSG_UI_START_CAPTION_TRACK = 6;
public static final int MSG_UI_STOP_CAPTION_TRACK = 7;
public static final int MSG_UI_RESET_CAPTION_TRACK = 8;
public static final int MSG_UI_CLEAR_CAPTION_RENDERER = 9;
public static final int MSG_UI_SET_STATUS_TEXT = 10;
public static final int MSG_UI_TOAST_RESCAN_NEEDED = 11;
private final Context mContext;
private final Handler mUiHandler;
private final View mOverlayView;
private final TextView mMessageView;
private final TextView mStatusView;
private final TextView mAudioStatusView;
private final ViewGroup mMessageLayout;
private final CaptionTrackRenderer mCaptionTrackRenderer;
private final TunerSessionWorker mSessionWorker;
private boolean mReleased = false;
private boolean mPlayPaused;
private long mTuneStartTimestamp;
public TunerSession(Context context, ChannelDataManager channelDataManager) {
super(context);
mContext = context;
mUiHandler = new Handler(this);
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mOverlayView = inflater.inflate(R.layout.ut_overlay_view, null);
mMessageLayout = (ViewGroup) mOverlayView.findViewById(R.id.message_layout);
mMessageLayout.setVisibility(View.INVISIBLE);
mMessageView = (TextView) mOverlayView.findViewById(R.id.message);
mStatusView = (TextView) mOverlayView.findViewById(R.id.tuner_status);
boolean showDebug = SystemPropertiesProxy.getBoolean(USBTUNER_SHOW_DEBUG, false);
mStatusView.setVisibility(showDebug ? View.VISIBLE : View.INVISIBLE);
mAudioStatusView = (TextView) mOverlayView.findViewById(R.id.audio_status);
mAudioStatusView.setVisibility(View.INVISIBLE);
CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption);
mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout);
mSessionWorker = new TunerSessionWorker(context, channelDataManager, this);
TunerPreferences.setTunerPreferencesChangedListener(this);
}
public boolean isReleased() {
return mReleased;
}
@Override
public View onCreateOverlayView() {
return mOverlayView;
}
@Override
public boolean onSelectTrack(int type, String trackId) {
mSessionWorker.sendMessage(TunerSessionWorker.MSG_SELECT_TRACK, type, 0, trackId);
return false;
}
@Override
public void onSetCaptionEnabled(boolean enabled) {
mSessionWorker.setCaptionEnabled(enabled);
}
@Override
public void onSetStreamVolume(float volume) {
mSessionWorker.setStreamVolume(volume);
}
@Override
public boolean onSetSurface(Surface surface) {
mSessionWorker.setSurface(surface);
return true;
}
@Override
public void onTimeShiftPause() {
mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_PAUSE);
mPlayPaused = true;
}
@Override
public void onTimeShiftResume() {
mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_RESUME);
mPlayPaused = false;
}
@Override
public void onTimeShiftSeekTo(long timeMs) {
if (DEBUG) Log.d(TAG, "Timeshift seekTo requested position: " + timeMs / 1000);
mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_SEEK_TO,
mPlayPaused ? 1 : 0, 0, timeMs);
}
@Override
public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
mSessionWorker.sendMessage(
TunerSessionWorker.MSG_TIMESHIFT_SET_PLAYBACKPARAMS, params);
}
@Override
public long onTimeShiftGetStartPosition() {
return mSessionWorker.getStartPosition();
}
@Override
public long onTimeShiftGetCurrentPosition() {
return mSessionWorker.getCurrentPosition();
}
@Override
public boolean onTune(Uri channelUri) {
if (DEBUG) {
Log.d(TAG, "onTune to " + channelUri != null ? channelUri.toString() : "");
}
if (channelUri == null) {
Log.w(TAG, "onTune() is failed due to null channelUri.");
mSessionWorker.stopTune();
return false;
}
mTuneStartTimestamp = SystemClock.elapsedRealtime();
mSessionWorker.tune(channelUri);
mPlayPaused = false;
return true;
}
@TargetApi(Build.VERSION_CODES.N)
@Override
public void onTimeShiftPlay(Uri recordUri) {
if (recordUri == null) {
Log.w(TAG, "onTimeShiftPlay() is failed due to null channelUri.");
mSessionWorker.stopTune();
return;
}
mTuneStartTimestamp = SystemClock.elapsedRealtime();
mSessionWorker.tune(recordUri);
mPlayPaused = false;
}
@Override
public void onUnblockContent(TvContentRating unblockedRating) {
mSessionWorker.sendMessage(TunerSessionWorker.MSG_UNBLOCKED_RATING,
unblockedRating);
}
@Override
public void onRelease() {
if (DEBUG) {
Log.d(TAG, "onRelease");
}
mReleased = true;
mSessionWorker.release();
mUiHandler.removeCallbacksAndMessages(null);
TunerPreferences.setTunerPreferencesChangedListener(null);
}
/**
* Sets {@link AudioCapabilities}.
*/
public void setAudioCapabilities(AudioCapabilities audioCapabilities) {
mSessionWorker.sendMessage(TunerSessionWorker.MSG_AUDIO_CAPABILITIES_CHANGED,
audioCapabilities);
}
@Override
public void notifyVideoAvailable() {
super.notifyVideoAvailable();
if (mTuneStartTimestamp != 0) {
Log.i(TAG, "[Profiler] Video available in "
+ (SystemClock.elapsedRealtime() - mTuneStartTimestamp) + " ms");
mTuneStartTimestamp = 0;
}
}
@Override
public void notifyVideoUnavailable(int reason) {
super.notifyVideoUnavailable(reason);
if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING
&& reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL) {
notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
}
}
public void sendUiMessage(int message) {
mUiHandler.sendEmptyMessage(message);
}
public void sendUiMessage(int message, Object object) {
mUiHandler.obtainMessage(message, object).sendToTarget();
}
public void sendUiMessage(int message, int arg1, int arg2, Object object) {
mUiHandler.obtainMessage(message, arg1, arg2, object).sendToTarget();
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_UI_SHOW_MESSAGE: {
mMessageView.setText((String) msg.obj);
mMessageLayout.setVisibility(View.VISIBLE);
return true;
}
case MSG_UI_HIDE_MESSAGE: {
mMessageLayout.setVisibility(View.INVISIBLE);
return true;
}
case MSG_UI_SHOW_AUDIO_UNPLAYABLE: {
// Showing message of enabling surround sound only when global surround sound
// setting is "never".
final int value = GlobalSettingsUtils.getEncodedSurroundOutputSettings(mContext);
if (value == GlobalSettingsUtils.ENCODED_SURROUND_OUTPUT_NEVER) {
mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML(
mContext.getString(R.string.ut_surround_sound_disabled))));
} else {
mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML(
mContext.getString(R.string.audio_passthrough_not_supported))));
}
mAudioStatusView.setVisibility(View.VISIBLE);
return true;
}
case MSG_UI_HIDE_AUDIO_UNPLAYABLE: {
mAudioStatusView.setVisibility(View.INVISIBLE);
return true;
}
case MSG_UI_PROCESS_CAPTION_TRACK: {
mCaptionTrackRenderer.processCaptionEvent((CaptionEvent) msg.obj);
return true;
}
case MSG_UI_START_CAPTION_TRACK: {
mCaptionTrackRenderer.start((AtscCaptionTrack) msg.obj);
return true;
}
case MSG_UI_STOP_CAPTION_TRACK: {
mCaptionTrackRenderer.stop();
return true;
}
case MSG_UI_RESET_CAPTION_TRACK: {
mCaptionTrackRenderer.reset();
return true;
}
case MSG_UI_CLEAR_CAPTION_RENDERER: {
mCaptionTrackRenderer.clear();
return true;
}
case MSG_UI_SET_STATUS_TEXT: {
mStatusView.setText((CharSequence) msg.obj);
return true;
}
case MSG_UI_TOAST_RESCAN_NEEDED: {
Toast.makeText(mContext, R.string.ut_rescan_needed, Toast.LENGTH_LONG).show();
return true;
}
}
return false;
}
@Override
public void onTunerPreferencesChanged() {
mSessionWorker.sendMessage(TunerSessionWorker.MSG_TUNER_PREFERENCES_CHANGED);
}
}