+A sample application that demonstrates how to implement TV input service.
+This sample uses captured ATSC streams which includes videos and channel
+and program information. On a setup request, this app registers channels and
+programs to the framework. Also, it plays the video when the framework tunes
+to the channel.
+
diff --git a/samples/AtscTvInput/res/raw/freq_1_prog_1.mpg b/samples/AtscTvInput/res/raw/freq_1_prog_1.mpg
new file mode 100644
index 000000000..276bcca57
Binary files /dev/null and b/samples/AtscTvInput/res/raw/freq_1_prog_1.mpg differ
diff --git a/samples/AtscTvInput/res/raw/freq_2_prog_1029.mpg b/samples/AtscTvInput/res/raw/freq_2_prog_1029.mpg
new file mode 100644
index 000000000..55a4a7651
Binary files /dev/null and b/samples/AtscTvInput/res/raw/freq_2_prog_1029.mpg differ
diff --git a/samples/AtscTvInput/res/values/strings.xml b/samples/AtscTvInput/res/values/strings.xml
new file mode 100644
index 000000000..815c82123
--- /dev/null
+++ b/samples/AtscTvInput/res/values/strings.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ AtscTvInput
+ ATSC Sample Input
+ Scanning channels, please wait...
+
diff --git a/samples/AtscTvInput/src/com/example/android/atsctvinput/AtscTvInputScanActivity.java b/samples/AtscTvInput/src/com/example/android/atsctvinput/AtscTvInputScanActivity.java
new file mode 100644
index 000000000..b313d7524
--- /dev/null
+++ b/samples/AtscTvInput/src/com/example/android/atsctvinput/AtscTvInputScanActivity.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 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.example.android.atsctvinput;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.ContentValues;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.TvContract;
+import android.util.Log;
+import android.util.Pair;
+
+import com.example.android.atsctvinput.SampleTsStream.TsStream;
+import com.example.android.atsctvinput.SectionParser.EITItem;
+import com.example.android.atsctvinput.SectionParser.VCTItem;
+import com.example.atsctvinput.R;
+
+import java.util.List;
+
+/**
+ * The scan/setup activity for ATSC TvInput app.
+ */
+public class AtscTvInputScanActivity extends Activity {
+ private static final String TAG = "AtscTvInputScanActivity";
+ private static final long FAKE_SCANTIME_PER_CHANNEL_MS = 1000;
+ private ProgressDialog mProgressDialog;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mProgressDialog = new ProgressDialog(this);
+ mProgressDialog.setMessage(getResources().getString(R.string.channel_scan_message));
+ mProgressDialog.setCancelable(false);
+
+ mProgressDialog.show();
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ clearChannels();
+ doAutoScan();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ mProgressDialog.hide();
+ AtscTvInputScanActivity.this.finish();
+ }
+ }.execute();
+ }
+
+ private void clearChannels() {
+ String selection = TvContract.Channels.SERVICE_NAME + " = ?";
+ String[] selectionArgs = new String[] {
+ AtscTvInputService.class.getName()
+ };
+ getContentResolver().delete(TvContract.Channels.CONTENT_URI, selection, selectionArgs);
+ }
+
+ private void doAutoScan() {
+ for (TsStream s : SampleTsStream.SAMPLES) {
+ Pair> result = SampleTsStream.extractChannelInfo(this, s);
+ if (result != null) {
+ insertChannel(result.first, s);
+ try {
+ Thread.sleep(FAKE_SCANTIME_PER_CHANNEL_MS);
+ } catch (InterruptedException e) {
+ // Do nothing.
+ }
+ }
+ }
+ }
+
+ public void insertChannel(VCTItem channel, TsStream stream) {
+ Log.d(TAG, "Channel " + channel.getShortName() + " " + channel.getMajorChannelNumber()
+ + "-" + channel.getMinorChannelNumber() + " is detected.");
+ ContentValues values = new ContentValues();
+ values.put(TvContract.Channels.SERVICE_NAME, AtscTvInputService.class.getName());
+ values.put(TvContract.Channels.DISPLAY_NUMBER,
+ channel.getMajorChannelNumber() + "-" + channel.getMinorChannelNumber());
+ values.put(TvContract.Channels.DISPLAY_NAME, channel.getShortName());
+ values.put(TvContract.Channels.DATA, SampleTsStream.getTuneInfo(stream));
+ getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);
+ }
+}
diff --git a/samples/AtscTvInput/src/com/example/android/atsctvinput/AtscTvInputService.java b/samples/AtscTvInput/src/com/example/android/atsctvinput/AtscTvInputService.java
new file mode 100644
index 000000000..bbb791119
--- /dev/null
+++ b/samples/AtscTvInput/src/com/example/android/atsctvinput/AtscTvInputService.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2014 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.example.android.atsctvinput;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.TvContract;
+import android.tv.TvInputService;
+import android.util.Log;
+import android.util.Pair;
+import android.view.Surface;
+
+import com.example.android.atsctvinput.SampleTsStream.TsStream;
+import com.example.android.atsctvinput.SectionParser.EITItem;
+import com.example.android.atsctvinput.SectionParser.VCTItem;
+
+import java.util.List;
+
+/**
+ * A sample TvInputService which plays ATSC TV stream.
+ */
+public class AtscTvInputService extends TvInputService {
+ private static final String TAG = "AtscTvInputService";
+ private static final int SEC_IN_MS = 1000;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(TAG, "onCreate()");
+ setAvailable(true);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(TAG, "onDestroy()");
+ }
+
+ @Override
+ public TvInputSessionImpl onCreateSession() {
+ return new MyTvInputSessionImpl();
+ }
+
+
+ public TsStream getTsStreamForChannel(Uri channelUri) {
+ String[] projection = { TvContract.Channels.DATA };
+ if (channelUri == null) {
+ return null;
+ }
+ Cursor cursor = this.getContentResolver().query(
+ channelUri, projection, null, null, null);
+ if (cursor == null) {
+ return null;
+ }
+ if (cursor.getCount() < 1) {
+ cursor.close();
+ return null;
+ }
+ cursor.moveToNext();
+ TsStream stream = SampleTsStream.getTsStreamFromTuneInfo(cursor.getString(0));
+ cursor.close();
+ return stream;
+ }
+
+ private class MyTvInputSessionImpl extends TvInputSessionImpl {
+ private MediaPlayer mPlayer;
+
+ protected MyTvInputSessionImpl() {
+ mPlayer = new MediaPlayer();
+ }
+
+ @Override
+ public void onRelease() {
+ if (mPlayer != null) {
+ mPlayer.release();
+ mPlayer = null;
+ }
+ }
+
+ @Override
+ public boolean onSetSurface(Surface surface) {
+ Log.d(TAG, "onSetSurface(" + surface + ")");
+ mPlayer.setSurface(surface);
+ return true;
+ }
+
+ @Override
+ public void onSetVolume(float volume) {
+ Log.d(TAG, "onSetVolume(" + volume + ")");
+ mPlayer.setVolume(volume, volume);
+ }
+
+ @Override
+ public boolean onTune(Uri channelUri) {
+ Log.d(TAG, "onTune(" + channelUri + ")");
+ mPlayer.reset();
+ TsStream stream = getTsStreamForChannel(channelUri);
+ if (stream == null) {
+ return false;
+ }
+ new ProgramUpdateTask().execute(stream, channelUri);
+ AssetFileDescriptor afd = getResources().openRawResourceFd(stream.mResourceId);
+ try {
+ mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
+ afd.getLength());
+ mPlayer.prepare();
+ afd.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to tune to(" + channelUri + ")");
+ mPlayer.reset();
+ return false;
+ }
+ mPlayer.start();
+ return true;
+ }
+ }
+
+ private class ProgramUpdateTask extends AsyncTask