Home | History | Annotate | Download | only in tvinput
      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