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.usbtuner.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.usbtuner.R;
41 import com.android.usbtuner.cc.CaptionLayout;
42 import com.android.usbtuner.cc.CaptionTrackRenderer;
43 import com.android.usbtuner.data.Cea708Data.CaptionEvent;
44 import com.android.usbtuner.data.Track.AtscCaptionTrack;
45 import com.android.usbtuner.data.TunerChannel;
46 import com.android.usbtuner.exoplayer.cache.CacheManager;
47 import com.android.usbtuner.util.StatusTextUtils;
48 import com.android.usbtuner.util.SystemPropertiesProxy;
49 
50 /**
51  * Provides a USB tuner TV input session. It handles Overlay UI works. Main tuner input functions
52  * are implemented in {@link TunerSessionWorker}.
53  */
54 public class TunerSession extends TvInputService.Session implements Handler.Callback {
55     private static final String TAG = "TunerSession";
56     private static final boolean DEBUG = false;
57     private static final String USBTUNER_SHOW_DEBUG = "persist.usbtuner.show_debug";
58 
59     public static final int MSG_UI_SHOW_MESSAGE = 1;
60     public static final int MSG_UI_HIDE_MESSAGE = 2;
61     public static final int MSG_UI_SHOW_AUDIO_UNPLAYABLE = 3;
62     public static final int MSG_UI_HIDE_AUDIO_UNPLAYABLE = 4;
63     public static final int MSG_UI_PROCESS_CAPTION_TRACK = 5;
64     public static final int MSG_UI_START_CAPTION_TRACK = 6;
65     public static final int MSG_UI_STOP_CAPTION_TRACK = 7;
66     public static final int MSG_UI_RESET_CAPTION_TRACK = 8;
67     public static final int MSG_UI_SET_STATUS_TEXT = 9;
68     public static final int MSG_UI_TOAST_RESCAN_NEEDED = 10;
69 
70     private final Context mContext;
71     private final Handler mUiHandler;
72     private final View mOverlayView;
73     private final TextView mMessageView;
74     private final TextView mStatusView;
75     private final TextView mAudioStatusView;
76     private final ViewGroup mMessageLayout;
77     private final CaptionTrackRenderer mCaptionTrackRenderer;
78     private final TunerSessionWorker mSessionWorker;
79     private boolean mReleased = false;
80     private boolean mVideoAvailable = false;
81     private boolean mPlayPaused;
82     private long mTuneStartTimestamp;
83 
TunerSession(Context context, ChannelDataManager channelDataManager, CacheManager cacheManager)84     public TunerSession(Context context, ChannelDataManager channelDataManager,
85             CacheManager cacheManager) {
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_ac3_passthrough_unavailable))));
102         CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption);
103         mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout);
104         mSessionWorker = new TunerSessionWorker(context, channelDataManager,
105                 cacheManager, 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(
120                 TunerSessionWorker.MSG_SELECT_TRACK, type, 0, trackId);
121         return false;
122     }
123 
124     @Override
onSetCaptionEnabled(boolean enabled)125     public void onSetCaptionEnabled(boolean enabled) {
126         mSessionWorker.sendMessage(
127                 TunerSessionWorker.MSG_SET_CAPTION_ENABLED, enabled);
128     }
129 
130     @Override
onSetStreamVolume(float volume)131     public void onSetStreamVolume(float volume) {
132         mSessionWorker.sendMessage(TunerSessionWorker.MSG_SET_STREAM_VOLUME, volume);
133     }
134 
135     @Override
onSetSurface(Surface surface)136     public boolean onSetSurface(Surface surface) {
137         mSessionWorker.sendMessage(TunerSessionWorker.MSG_SET_SURFACE, surface);
138         return true;
139     }
140 
141     @Override
onTimeShiftPause()142     public void onTimeShiftPause() {
143         mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_PAUSE);
144         mPlayPaused = true;
145     }
146 
147     @Override
onTimeShiftResume()148     public void onTimeShiftResume() {
149         mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_RESUME);
150         mPlayPaused = false;
151     }
152 
153     @Override
onTimeShiftSeekTo(long timeMs)154     public void onTimeShiftSeekTo(long timeMs) {
155         if (DEBUG) Log.d(TAG, "Timeshift seekTo requested position: " + timeMs / 1000);
156         mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_SEEK_TO,
157                 mPlayPaused ? 1 : 0, 0, timeMs);
158     }
159 
160     @Override
onTimeShiftSetPlaybackParams(PlaybackParams params)161     public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
162         mSessionWorker.sendMessage(
163                 TunerSessionWorker.MSG_TIMESHIFT_SET_PLAYBACKPARAMS, params);
164     }
165 
166     @Override
onTimeShiftGetStartPosition()167     public long onTimeShiftGetStartPosition() {
168         return mSessionWorker.getStartPosition();
169     }
170 
171     @Override
onTimeShiftGetCurrentPosition()172     public long onTimeShiftGetCurrentPosition() {
173         return mSessionWorker.getCurrentPosition();
174     }
175 
176     @Override
onTune(Uri channelUri)177     public boolean onTune(Uri channelUri) {
178         if (DEBUG) {
179             Log.d(TAG, "onTune to " + channelUri != null ? channelUri.toString() : "");
180         }
181         if (channelUri == null) {
182             Log.w(TAG, "onTune() is failed due to null channelUri.");
183             mSessionWorker.stopTune();
184             return false;
185         }
186         mTuneStartTimestamp = SystemClock.elapsedRealtime();
187         mSessionWorker.tune(channelUri);
188         mPlayPaused = false;
189         return true;
190     }
191 
192     @TargetApi(Build.VERSION_CODES.N)
193     @Override
onTimeShiftPlay(Uri recordUri)194     public void onTimeShiftPlay(Uri recordUri) {
195         if (recordUri == null) {
196             Log.w(TAG, "onTimeShiftPlay() is failed due to null channelUri.");
197             mSessionWorker.stopTune();
198             return;
199         }
200         mTuneStartTimestamp = SystemClock.elapsedRealtime();
201         mSessionWorker.tune(recordUri);
202         mPlayPaused = false;
203     }
204 
205     @Override
onUnblockContent(TvContentRating unblockedRating)206     public void onUnblockContent(TvContentRating unblockedRating) {
207         mSessionWorker.sendMessage(TunerSessionWorker.MSG_UNBLOCKED_RATING,
208                 unblockedRating);
209     }
210 
211     @Override
onRelease()212     public void onRelease() {
213         if (DEBUG) {
214             Log.d(TAG, "onRelease");
215         }
216         mReleased = true;
217         mSessionWorker.release();
218         mUiHandler.removeCallbacksAndMessages(null);
219     }
220 
221     /**
222      * Sets {@link AudioCapabilities}.
223      */
setAudioCapabilities(AudioCapabilities audioCapabilities)224     public void setAudioCapabilities(AudioCapabilities audioCapabilities) {
225         mSessionWorker.sendMessage(TunerSessionWorker.MSG_AUDIO_CAPABILITIES_CHANGED,
226                 audioCapabilities);
227     }
228 
229     @Override
notifyVideoAvailable()230     public void notifyVideoAvailable() {
231         super.notifyVideoAvailable();
232         if (mTuneStartTimestamp != 0) {
233             Log.i(TAG, "[Profiler] Video available in "
234                     + (SystemClock.elapsedRealtime() - mTuneStartTimestamp) + " ms");
235             mTuneStartTimestamp = 0;
236         }
237         mVideoAvailable = true;
238     }
239 
240     @Override
notifyVideoUnavailable(int reason)241     public void notifyVideoUnavailable(int reason) {
242         switch (reason) {
243             case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
244             case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
245             case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:
246             case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
247                 super.notifyVideoUnavailable(reason);
248                 break;
249             case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
250             default:
251                 super.notifyVideoAvailable();
252                 TunerChannel channel = mSessionWorker.getCurrentChannel();
253                 if (channel != null) {
254                     sendUiMessage(TunerSession.MSG_UI_SHOW_MESSAGE,
255                             mContext.getString(R.string.ut_fail_to_tune, channel.getName()));
256                 } else {
257                     sendUiMessage(TunerSession.MSG_UI_SHOW_MESSAGE,
258                             mContext.getString(R.string.ut_fail_to_tune_to_unknown_channel));
259                 }
260                 break;
261         }
262         if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING
263                 && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL) {
264             notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
265         }
266     }
267 
sendUiMessage(int message)268     public void sendUiMessage(int message) {
269         mUiHandler.sendEmptyMessage(message);
270     }
271 
sendUiMessage(int message, Object object)272     public void sendUiMessage(int message, Object object) {
273         mUiHandler.obtainMessage(message, object).sendToTarget();
274     }
275 
sendUiMessage(int message, int arg1, int arg2, Object object)276     public void sendUiMessage(int message, int arg1, int arg2, Object object) {
277         mUiHandler.obtainMessage(message, arg1, arg2, object).sendToTarget();
278     }
279 
280     @Override
handleMessage(Message msg)281     public boolean handleMessage(Message msg) {
282         switch (msg.what) {
283             case MSG_UI_SHOW_MESSAGE: {
284                 mMessageView.setText((String) msg.obj);
285                 mMessageLayout.setVisibility(View.VISIBLE);
286                 return true;
287             }
288             case MSG_UI_HIDE_MESSAGE: {
289                 mMessageLayout.setVisibility(View.INVISIBLE);
290                 return true;
291             }
292             case MSG_UI_SHOW_AUDIO_UNPLAYABLE: {
293                 mAudioStatusView.setVisibility(View.VISIBLE);
294                 return true;
295             }
296             case MSG_UI_HIDE_AUDIO_UNPLAYABLE: {
297                 mAudioStatusView.setVisibility(View.INVISIBLE);
298                 return true;
299             }
300             case MSG_UI_PROCESS_CAPTION_TRACK: {
301                 mCaptionTrackRenderer.processCaptionEvent((CaptionEvent) msg.obj);
302                 return true;
303             }
304             case MSG_UI_START_CAPTION_TRACK: {
305                 mCaptionTrackRenderer.start((AtscCaptionTrack) msg.obj);
306                 return true;
307             }
308             case MSG_UI_STOP_CAPTION_TRACK: {
309                 mCaptionTrackRenderer.stop();
310                 return true;
311             }
312             case MSG_UI_RESET_CAPTION_TRACK: {
313                 mCaptionTrackRenderer.reset();
314                 return true;
315             }
316             case MSG_UI_SET_STATUS_TEXT: {
317                 mStatusView.setText((CharSequence) msg.obj);
318                 return true;
319             }
320             case MSG_UI_TOAST_RESCAN_NEEDED: {
321                 Toast.makeText(mContext, R.string.ut_rescan_needed, Toast.LENGTH_LONG).show();
322                 return true;
323             }
324         }
325         return false;
326     }
327 }
328