• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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