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