From 3d4af7c8f79a33355c27bd5a75698e3ee9e3d7c3 Mon Sep 17 00:00:00 2001 From: shubang Date: Thu, 16 Jul 2020 13:48:44 -0700 Subject: Tuner TIS: set up Frontend, Filter and DvrPlayback Bug: 158814011 Test: manually on cuttlefish Change-Id: I4a66847c942b7a8892ef28f8c103f5d500467fda --- .../SampleTunerTvInputService.java | 169 ++++++++++++++++++++- 1 file changed, 161 insertions(+), 8 deletions(-) diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java index 6ac95353..28e8edd2 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -1,13 +1,27 @@ package com.android.tv.samples.sampletunertvinput; import android.content.Context; +import android.media.tv.tuner.dvr.DvrPlayback; +import android.media.tv.tuner.dvr.DvrSettings; +import android.media.tv.tuner.filter.AvSettings; +import android.media.tv.tuner.filter.Filter; +import android.media.tv.tuner.filter.FilterCallback; +import android.media.tv.tuner.filter.FilterEvent; +import android.media.tv.tuner.filter.TsFilterConfiguration; import android.media.tv.tuner.frontend.AtscFrontendSettings; +import android.media.tv.tuner.frontend.DvbtFrontendSettings; import android.media.tv.tuner.frontend.FrontendSettings; +import android.media.tv.tuner.frontend.OnTuneEventListener; import android.media.tv.tuner.Tuner; import android.media.tv.TvInputService; import android.net.Uri; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.ParcelFileDescriptor; import android.util.Log; import android.view.Surface; +import java.io.File; +import java.io.FileNotFoundException; /** SampleTunerTvInputService */ @@ -15,8 +29,19 @@ public class SampleTunerTvInputService extends TvInputService { private static final String TAG = "SampleTunerTvInput"; private static final boolean DEBUG = true; + private static final int AUDIO_TPID = 257; + private static final int VIDEO_TPID = 256; + private static final int STATUS_MASK = 0xf; + private static final int LOW_THRESHOLD = 0x1000; + private static final int HIGH_THRESHOLD = 0x07fff; + private static final int FREQUENCY = 578000; + private static final int PACKET_SIZE = 188; + private static final int FILTER_BUFFER_SIZE = 16000000; + private static final int DVR_BUFFER_SIZE = 4000000; + private static final int INPUT_FILE_MAX_SIZE = 700000; + public static final String INPUT_ID = - "com.android.tv.samples.sampletunertvinput/.SampleTunerTvInputService"; + "com.android.tv.samples.sampletunertvinput/.SampleTunerTvInputService"; private String mSessionId; @Override @@ -38,6 +63,11 @@ public class SampleTunerTvInputService extends TvInputService { private Surface surface; private final Context mContext; + private Handler mHandler; + + private Filter audioFilter; + private Filter videoFilter; + private DvrPlayback dvr; Tuner tuner; @@ -77,16 +107,19 @@ public class SampleTunerTvInputService extends TvInputService { tuner = new Tuner(mContext, mSessionId, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); + mHandler = new Handler(); + audioFilter = audioFilter(); + videoFilter = videoFilter(); + audioFilter.start(); + videoFilter.start(); + int feCount = tuner.getFrontendIds().size(); if (feCount <= 0) return false; - AtscFrontendSettings settings = - AtscFrontendSettings - .builder() - .setFrequency(2000) - .setModulation(AtscFrontendSettings.MODULATION_AUTO) - .build(); - tuner.tune(settings); + tune(); + // use dvr playback to feed the data on platform without physical tuner + dvr = dvrPlayback(); + dvr.start(); return true; } @@ -97,5 +130,125 @@ public class SampleTunerTvInputService extends TvInputService { Log.d(TAG, "onSetCaptionEnabled " + b); } } + + private Filter audioFilter() { + Filter audioFilter = tuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_AUDIO, + FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler), + new FilterCallback() { + @Override + public void onFilterEvent(Filter filter, FilterEvent[] events) { + if (DEBUG) { + Log.d(TAG, "onFilterEvent audio, size=" + events.length); + for (int i = 0; i < events.length; i++) { + Log.d(TAG, "events[" + i + "] is " + + events[i].getClass().getSimpleName()); + } + } + } + + @Override + public void onFilterStatusChanged(Filter filter, int status) { + if (DEBUG) { + Log.d(TAG, "onFilterEvent audio, status=" + status); + } + } + }); + AvSettings settings = + AvSettings.builder(Filter.TYPE_TS, true).setPassthrough(false).build(); + audioFilter.configure( + TsFilterConfiguration.builder().setTpid(AUDIO_TPID) + .setSettings(settings).build()); + return audioFilter; + } + + private Filter videoFilter() { + Filter videoFilter = tuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_VIDEO, + FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler), + new FilterCallback() { + @Override + public void onFilterEvent(Filter filter, FilterEvent[] events) { + if (DEBUG) { + Log.d(TAG, "onFilterEvent video, size=" + events.length); + for (int i = 0; i < events.length; i++) { + Log.d(TAG, "events[" + i + "] is " + + events[i].getClass().getSimpleName()); + } + } + } + + @Override + public void onFilterStatusChanged(Filter filter, int status) { + if (DEBUG) { + Log.d(TAG, "onFilterEvent video, status=" + status); + } + } + }); + AvSettings settings = + AvSettings.builder(Filter.TYPE_TS, false).setPassthrough(false).build(); + videoFilter.configure( + TsFilterConfiguration.builder().setTpid(VIDEO_TPID) + .setSettings(settings).build()); + return videoFilter; + } + + private DvrPlayback dvrPlayback() { + DvrPlayback dvr = tuner.openDvrPlayback(DVR_BUFFER_SIZE, new HandlerExecutor(mHandler), + status -> { + if (DEBUG) { + Log.d(TAG, "onPlaybackStatusChanged status=" + status); + } + }); + int res = dvr.configure( + DvrSettings.builder() + .setStatusMask(STATUS_MASK) + .setLowThreshold(LOW_THRESHOLD) + .setHighThreshold(HIGH_THRESHOLD) + .setDataFormat(DvrSettings.DATA_FORMAT_ES) + .setPacketSize(PACKET_SIZE) + .build()); + if (DEBUG) { + Log.d(TAG, "config res=" + res); + } + File file = new File("/data/local/tmp/test.es"); + if (file.exists()) { + try { + dvr.setFileDescriptor( + ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)); + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to create FD"); + } + } else { + Log.w(TAG, "File not existing"); + } + return dvr; + } + + private void tune() { + DvbtFrontendSettings feSettings = DvbtFrontendSettings.builder() + .setFrequency(FREQUENCY) + .setTransmissionMode(DvbtFrontendSettings.TRANSMISSION_MODE_AUTO) + .setBandwidth(DvbtFrontendSettings.BANDWIDTH_8MHZ) + .setConstellation(DvbtFrontendSettings.CONSTELLATION_AUTO) + .setHierarchy(DvbtFrontendSettings.HIERARCHY_AUTO) + .setHighPriorityCodeRate(DvbtFrontendSettings.CODERATE_AUTO) + .setLowPriorityCodeRate(DvbtFrontendSettings.CODERATE_AUTO) + .setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_AUTO) + .setHighPriority(true) + .setStandard(DvbtFrontendSettings.STANDARD_T) + .build(); + tuner.setOnTuneEventListener(new HandlerExecutor(mHandler), new OnTuneEventListener() { + @Override + public void onTuneEvent(int tuneEvent) { + if (DEBUG) { + Log.d(TAG, "onTuneEvent " + tuneEvent); + } + long read = dvr.read(INPUT_FILE_MAX_SIZE); + if (DEBUG) { + Log.d(TAG, "read=" + read); + } + } + }); + tuner.tune(feSettings); + } } } \ No newline at end of file -- cgit v1.2.3 From 325811a8451af64d1e87fe7b5852d53348fb1d9c Mon Sep 17 00:00:00 2001 From: shubang Date: Mon, 20 Jul 2020 19:29:29 -0700 Subject: Tuner TIS: use MediaCodec to play video Bug: 161481210 Test: cuttlefish Change-Id: Iddbce0a4aaef4bdd9de68a69063a1ec35ad4789f --- .../SampleTunerTvInputService.java | 264 ++++++++++++++++++--- 1 file changed, 230 insertions(+), 34 deletions(-) diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java index 28e8edd2..e86ced14 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -1,12 +1,18 @@ package com.android.tv.samples.sampletunertvinput; +import static android.media.tv.TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN; + import android.content.Context; +import android.media.MediaCodec; +import android.media.MediaCodec.BufferInfo; +import android.media.MediaFormat; import android.media.tv.tuner.dvr.DvrPlayback; import android.media.tv.tuner.dvr.DvrSettings; import android.media.tv.tuner.filter.AvSettings; import android.media.tv.tuner.filter.Filter; import android.media.tv.tuner.filter.FilterCallback; import android.media.tv.tuner.filter.FilterEvent; +import android.media.tv.tuner.filter.MediaEvent; import android.media.tv.tuner.filter.TsFilterConfiguration; import android.media.tv.tuner.frontend.AtscFrontendSettings; import android.media.tv.tuner.frontend.DvbtFrontendSettings; @@ -22,6 +28,12 @@ import android.util.Log; import android.view.Surface; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; /** SampleTunerTvInputService */ @@ -35,10 +47,30 @@ public class SampleTunerTvInputService extends TvInputService { private static final int LOW_THRESHOLD = 0x1000; private static final int HIGH_THRESHOLD = 0x07fff; private static final int FREQUENCY = 578000; - private static final int PACKET_SIZE = 188; private static final int FILTER_BUFFER_SIZE = 16000000; private static final int DVR_BUFFER_SIZE = 4000000; private static final int INPUT_FILE_MAX_SIZE = 700000; + private static final int PACKET_SIZE = 188; + + private static final int TIMEOUT_US = 100000; + private static final boolean SAVE_DATA = true; + private static final String ES_PATH = "/data/local/tmp/test.es"; + private static final MediaFormat VIDEO_FORMAT; + + static { + // format extracted for the specific input file + VIDEO_FORMAT = MediaFormat.createVideoFormat("video/avc", 320, 240); + VIDEO_FORMAT.setInteger(MediaFormat.KEY_TRACK_ID, 1); + VIDEO_FORMAT.setLong(MediaFormat.KEY_DURATION, 9933333); + VIDEO_FORMAT.setInteger(MediaFormat.KEY_LEVEL, 32); + VIDEO_FORMAT.setInteger(MediaFormat.KEY_PROFILE, 65536); + ByteBuffer csd = ByteBuffer.wrap( + new byte[] {0, 0, 0, 1, 103, 66, -64, 20, -38, 5, 7, -24, 64, 0, 0, 3, 0, 64, 0, + 0, 15, 35, -59, 10, -88}); + VIDEO_FORMAT.setByteBuffer("csd-0", csd); + csd = ByteBuffer.wrap(new byte[] {0, 0, 0, 1, 104, -50, 60, -128}); + VIDEO_FORMAT.setByteBuffer("csd-1", csd); + } public static final String INPUT_ID = "com.android.tv.samples.sampletunertvinput/.SampleTunerTvInputService"; @@ -61,14 +93,19 @@ public class SampleTunerTvInputService extends TvInputService { class TvInputSessionImpl extends Session { - private Surface surface; private final Context mContext; private Handler mHandler; - private Filter audioFilter; - private Filter videoFilter; - private DvrPlayback dvr; - Tuner tuner; + private Surface mSurface; + private Filter mAudioFilter; + private Filter mVideoFilter; + private DvrPlayback mDvr; + private Tuner mTuner; + private MediaCodec mMediaCodec; + private Thread mDecoderThread; + private Deque mDataQueue; + private List mSavedData; + private boolean mDataReady = false; public TvInputSessionImpl(Context context) { @@ -81,6 +118,28 @@ public class SampleTunerTvInputService extends TvInputService { if (DEBUG) { Log.d(TAG, "onRelease"); } + if (mDecoderThread != null) { + mDecoderThread.interrupt(); + mDecoderThread = null; + } + if (mMediaCodec != null) { + mMediaCodec.release(); + mMediaCodec = null; + } + if (mAudioFilter != null) { + mAudioFilter.close(); + } + if (mVideoFilter != null) { + mVideoFilter.close(); + } + if (mDvr != null) { + mDvr.close(); + } + if (mTuner != null) { + mTuner.close(); + } + mDataQueue = null; + mSavedData = null; } @Override @@ -88,7 +147,7 @@ public class SampleTunerTvInputService extends TvInputService { if (DEBUG) { Log.d(TAG, "onSetSurface"); } - this.surface = surface; + this.mSurface = surface; return true; } @@ -104,23 +163,16 @@ public class SampleTunerTvInputService extends TvInputService { if (DEBUG) { Log.d(TAG, "onTune " + uri); } - tuner = new Tuner(mContext, mSessionId, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); - + if (!initCodec()) { + Log.e(TAG, "null codec!"); + return false; + } mHandler = new Handler(); - audioFilter = audioFilter(); - videoFilter = videoFilter(); - audioFilter.start(); - videoFilter.start(); - - int feCount = tuner.getFrontendIds().size(); - if (feCount <= 0) return false; - - tune(); - // use dvr playback to feed the data on platform without physical tuner - dvr = dvrPlayback(); - dvr.start(); - + mDecoderThread = + new Thread( + this::decodeInternal, + "sample-tuner-tis-decoder-thread"); + mDecoderThread.start(); return true; } @@ -132,16 +184,18 @@ public class SampleTunerTvInputService extends TvInputService { } private Filter audioFilter() { - Filter audioFilter = tuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_AUDIO, + Filter audioFilter = mTuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_AUDIO, FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler), new FilterCallback() { @Override public void onFilterEvent(Filter filter, FilterEvent[] events) { if (DEBUG) { Log.d(TAG, "onFilterEvent audio, size=" + events.length); - for (int i = 0; i < events.length; i++) { + } + for (int i = 0; i < events.length; i++) { + if (DEBUG) { Log.d(TAG, "events[" + i + "] is " - + events[i].getClass().getSimpleName()); + + events[i].getClass().getSimpleName()); } } } @@ -162,16 +216,25 @@ public class SampleTunerTvInputService extends TvInputService { } private Filter videoFilter() { - Filter videoFilter = tuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_VIDEO, + Filter videoFilter = mTuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_VIDEO, FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler), new FilterCallback() { @Override public void onFilterEvent(Filter filter, FilterEvent[] events) { if (DEBUG) { Log.d(TAG, "onFilterEvent video, size=" + events.length); - for (int i = 0; i < events.length; i++) { + } + for (int i = 0; i < events.length; i++) { + if (DEBUG) { Log.d(TAG, "events[" + i + "] is " - + events[i].getClass().getSimpleName()); + + events[i].getClass().getSimpleName()); + } + if (events[i] instanceof MediaEvent) { + MediaEvent me = (MediaEvent) events[i]; + mDataQueue.add(me); + if (SAVE_DATA) { + mSavedData.add(me); + } } } } @@ -180,6 +243,9 @@ public class SampleTunerTvInputService extends TvInputService { public void onFilterStatusChanged(Filter filter, int status) { if (DEBUG) { Log.d(TAG, "onFilterEvent video, status=" + status); + if (status == Filter.STATUS_DATA_READY) { + mDataReady = true; + } } } }); @@ -192,7 +258,7 @@ public class SampleTunerTvInputService extends TvInputService { } private DvrPlayback dvrPlayback() { - DvrPlayback dvr = tuner.openDvrPlayback(DVR_BUFFER_SIZE, new HandlerExecutor(mHandler), + DvrPlayback dvr = mTuner.openDvrPlayback(DVR_BUFFER_SIZE, new HandlerExecutor(mHandler), status -> { if (DEBUG) { Log.d(TAG, "onPlaybackStatusChanged status=" + status); @@ -209,7 +275,7 @@ public class SampleTunerTvInputService extends TvInputService { if (DEBUG) { Log.d(TAG, "config res=" + res); } - File file = new File("/data/local/tmp/test.es"); + File file = new File(ES_PATH); if (file.exists()) { try { dvr.setFileDescriptor( @@ -236,19 +302,149 @@ public class SampleTunerTvInputService extends TvInputService { .setHighPriority(true) .setStandard(DvbtFrontendSettings.STANDARD_T) .build(); - tuner.setOnTuneEventListener(new HandlerExecutor(mHandler), new OnTuneEventListener() { + mTuner.setOnTuneEventListener(new HandlerExecutor(mHandler), new OnTuneEventListener() { @Override public void onTuneEvent(int tuneEvent) { if (DEBUG) { Log.d(TAG, "onTuneEvent " + tuneEvent); } - long read = dvr.read(INPUT_FILE_MAX_SIZE); + long read = mDvr.read(INPUT_FILE_MAX_SIZE); if (DEBUG) { Log.d(TAG, "read=" + read); } } }); - tuner.tune(feSettings); + mTuner.tune(feSettings); + } + + private boolean initCodec() { + if (mMediaCodec != null) { + mMediaCodec.release(); + mMediaCodec = null; + } + try { + mMediaCodec = MediaCodec.createDecoderByType("video/avc"); + mMediaCodec.configure(VIDEO_FORMAT, mSurface, null, 0); + } catch (IOException e) { + Log.e(TAG, "Error: " + e.getMessage()); + } + + if (mMediaCodec == null) { + Log.e(TAG, "null codec!"); + notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_UNKNOWN); + return false; + } + return true; } + + private void decodeInternal() { + mDataQueue = new ArrayDeque<>(); + mSavedData = new ArrayList<>(); + mTuner = new Tuner(mContext, mSessionId, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); + + mAudioFilter = audioFilter(); + mVideoFilter = videoFilter(); + mAudioFilter.start(); + mVideoFilter.start(); + // use dvr playback to feed the data on platform without physical tuner + mDvr = dvrPlayback(); + tune(); + mDvr.start(); + mMediaCodec.start(); + + try { + while (!Thread.interrupted()) { + if (!mDataReady) { + Thread.sleep(100); + } + if (!mDataQueue.isEmpty()) { + if (queueCodecInputBuffer(mDataQueue.getFirst())) { + // data consumed, remove. + mDataQueue.pollFirst(); + } + } else if (SAVE_DATA) { + mDataQueue.addAll(mSavedData); + } + releaseCodecOutputBuffer(); + } + } catch (Exception e) { + Log.e(TAG, "Error: " + e.getMessage()); + } + } + + private boolean queueCodecInputBuffer(MediaEvent mediaEvent) { + int res = mMediaCodec.dequeueInputBuffer(TIMEOUT_US); + if (res >= 0) { + ByteBuffer buffer = mMediaCodec.getInputBuffer(res); + if (buffer == null) { + throw new RuntimeException("Null decoder input buffer"); + } + + ByteBuffer data = mediaEvent.getLinearBlock().map(); + int sampleSize = (int) mediaEvent.getDataLength(); + int offset = (int) mediaEvent.getOffset(); + long pts = mediaEvent.getPts(); + + if (offset > 0 && offset < data.limit()) { + data.position(offset); + } else { + data.position(0); + } + + if (DEBUG) { + Log.d( + TAG, + "Decoder: Send data to decoder." + + " Sample size=" + + sampleSize + + " pts=" + + pts + + " limit=" + + data.limit() + + " pos=" + + data.position() + + " size=" + + (data.limit() - data.position())); + } + while (data.position() < data.limit()) { + // fill codec input buffer + buffer.put(data.get()); + } + + mMediaCodec.queueInputBuffer(res, 0, sampleSize, pts, 0); + } else { + Log.d(TAG, "queueCodecInputBuffer res=" + res); + return false; + } + return true; + } + + private void releaseCodecOutputBuffer() { + // play frames + BufferInfo bufferInfo = new BufferInfo(); + int res = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US); + if (res >= 0) { + mMediaCodec.releaseOutputBuffer(res, true); + notifyVideoAvailable(); + if (DEBUG) { + Log.d(TAG, "notifyVideoAvailable"); + } + } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + MediaFormat format = mMediaCodec.getOutputFormat(); + if (DEBUG) { + Log.d(TAG, "releaseCodecOutputBuffer: Output format changed:" + format); + } + } else if (res == MediaCodec.INFO_TRY_AGAIN_LATER) { + if (DEBUG) { + Log.d(TAG, "releaseCodecOutputBuffer: timeout"); + } + } else { + if (DEBUG) { + Log.d(TAG, "Return value of releaseCodecOutputBuffer:" + res); + } + } + } + } } \ No newline at end of file -- cgit v1.2.3 From 0a3b782d6e460a3621cc7b947595427db08f33f6 Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Tue, 15 Sep 2020 20:10:54 +0200 Subject: Add android:exported=true Required for Android S+ Test: - Bug: 168209089 Change-Id: I88eca15e8b80c902199e966eb28559abf1743c81 --- tuner/sampletunertvinput/AndroidManifest.xml | 6 ++++-- .../com/android/tv/samples/sampletunertvinput/AndroidManifest.xml | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tuner/sampletunertvinput/AndroidManifest.xml b/tuner/sampletunertvinput/AndroidManifest.xml index d282889a..8b25d0bf 100644 --- a/tuner/sampletunertvinput/AndroidManifest.xml +++ b/tuner/sampletunertvinput/AndroidManifest.xml @@ -43,7 +43,8 @@ android:theme="@android:style/Theme.Holo.Light.NoActionBar" android:appComponentFactory="android.support.v4.app.CoreComponentFactory" > - + @@ -51,7 +52,8 @@ + android:process="com.android.tv.samples.sampletunertvinput" + android:exported="true"> diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml index 8fc96b2a..909e2431 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml @@ -39,7 +39,8 @@ android:icon="@mipmap/ic_launcher" android:theme="@android:style/Theme.Holo.Light.NoActionBar" android:appComponentFactory="android.support.v4.app.CoreComponentFactory" > - + @@ -47,7 +48,8 @@ + android:process="com.android.tv.samples.sampletunertvinput" + android:exported="true"> -- cgit v1.2.3 From eab43fcd3d27a516a69efaabe03ed63cb015445c Mon Sep 17 00:00:00 2001 From: Robin Lee Date: Wed, 25 Nov 2020 12:04:37 +0100 Subject: Move com.google.android.tv.installed to livetv app Change-Id: I2c09ba927a4c92e1b1400c53de1f615811640306 --- com.android.tv.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.android.tv.xml b/com.android.tv.xml index 245d275d..1379a010 100644 --- a/com.android.tv.xml +++ b/com.android.tv.xml @@ -1,5 +1,7 @@ + + -- cgit v1.2.3 From 865b24ebe79cb88dd738c3749379ef1fe82b48f6 Mon Sep 17 00:00:00 2001 From: Hongguang Chen Date: Thu, 11 Feb 2021 22:38:43 -0800 Subject: Handle com.android.tv.action.VIEW_INPUTS intent. This is to show "Input" in launcher dashboard. https://drive.google.com/file/d/1ezLobxq8pt35Va6a1QTCNflhC1cZYbyk/view Bug: 180076278 Test: manual Change-Id: Ifba3e0383f95050e1a4e64b00a9098c1d6b345d0 --- AndroidManifest.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 854f2883..38d24747 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -165,7 +165,12 @@ + android:theme="@style/Theme.SelectInputActivity"> + + + + + Date: Thu, 18 Mar 2021 15:11:13 -0700 Subject: Allow to start background Activity. This is to allow start SelectInputActivity by Global key. Bug: 183140817 Test: manual Change-Id: I39965f2947638249699d2f6a94e23ea682a78acd --- AndroidManifest.xml | 1 + com.android.tv.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 38d24747..88f4e33a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -34,6 +34,7 @@ + diff --git a/com.android.tv.xml b/com.android.tv.xml index 1379a010..5ac6c0c3 100644 --- a/com.android.tv.xml +++ b/com.android.tv.xml @@ -9,6 +9,7 @@ + -- cgit v1.2.3 From 96d125af115e720868973965dfb836b36ab1cd26 Mon Sep 17 00:00:00 2001 From: Kensuke Miyagi Date: Mon, 22 Mar 2021 13:16:18 -0700 Subject: Add android.intent.category.DEFAULT to TvActivity Bug: 183430343 Test: Confirmed TvApp shows up in Basic mode as well Change-Id: I8d97721621524335e00f09854d183257fcee12ad --- AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 88f4e33a..2cff92d3 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -116,6 +116,7 @@ + Date: Wed, 24 Mar 2021 17:12:44 -0700 Subject: Broadcast inputId upon selection to LauncherX This covers both internal tuner and external input such as HDMI. Bug: 183657212 Test: Manaully confirmed its behavior to the spec Change-Id: I9b4167cd791bf537820e31b831d02682965e1c17 --- src/com/android/tv/MainActivity.java | 7 ++++- src/com/android/tv/util/GtvUtils.java | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/com/android/tv/util/GtvUtils.java diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java index 9c615b93..8dbafe47 100644 --- a/src/com/android/tv/MainActivity.java +++ b/src/com/android/tv/MainActivity.java @@ -149,6 +149,7 @@ import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.AsyncDbTask.DbExecutor; import com.android.tv.util.CaptionSettings; +import com.android.tv.util.GtvUtils; import com.android.tv.util.OnboardingUtils; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; @@ -456,7 +457,11 @@ public class MainActivity extends Activity } @Override - public void onChannelChanged(Channel previousChannel, Channel currentChannel) {} + public void onChannelChanged(Channel previousChannel, Channel currentChannel) { + if (currentChannel != null) { + GtvUtils.broadcastInputId(MainActivity.this, currentChannel.getInputId()); + } + } }; private final Runnable mRestoreMainViewRunnable = this::restoreMainTvView; diff --git a/src/com/android/tv/util/GtvUtils.java b/src/com/android/tv/util/GtvUtils.java new file mode 100644 index 00000000..eb50e062 --- /dev/null +++ b/src/com/android/tv/util/GtvUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 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.android.tv.util; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.util.Log; + +/** A utility class for Google TV */ +public class GtvUtils { + private static final String TAG = "GtvUtils"; + private static final String AMATI_FEATURE = "com.google.android.feature.AMATI_EXPERIENCE"; + private static final String PERMISSION_WRITE_EPG_DATA = + "com.android.providers.tv.permission.WRITE_EPG_DATA"; + private static final String ACTION_INPUT_SELECTED = "android.apps.tv.launcherx.INPUT_SELECTED"; + private static final String EXTRA_INPUT_ID = "extra_input_id"; + private static final String LAUNCHERX_PACKAGE_NAME = "com.google.android.apps.tv.launcherx"; + private static Boolean mEnabled = null; + + private static boolean isEnabled(Context context) { + if (mEnabled == null) { + PackageManager pm = context.getPackageManager(); + mEnabled = pm.hasSystemFeature(AMATI_FEATURE); + } + return mEnabled; + } + + /** Broadcasts the intent with inputId to the Launcher */ + public static void broadcastInputId(Context context, String inputId) { + if (isEnabled(context)) { + if (inputId == null) { + Log.e(TAG, "Will not broadcast inputId because it is null"); + } else { + Intent intent = new Intent(ACTION_INPUT_SELECTED); + intent.putExtra(EXTRA_INPUT_ID, inputId); + intent.setPackage(LAUNCHERX_PACKAGE_NAME); + context.sendBroadcast(intent, PERMISSION_WRITE_EPG_DATA); + } + } + } +} -- cgit v1.2.3 From 8ef24b8bbca4fb5ffb8c65b2b3a5bf4d89366de0 Mon Sep 17 00:00:00 2001 From: Pedro Loureiro Date: Thu, 25 Feb 2021 15:32:01 +0000 Subject: Add lint baseline to address NewApi errors We are enabling a new lint check where the min sdk != compile sdk. It has produced a lot of errors and adding the baseline file(s) allows us to continue work without introducing more problems. Bug: 150847901 Test: m lint-check Change-Id: I0f742764a5be7e225ffbea88eb767c155658c66c --- common/lint-baseline.xml | 15 ++ lint-baseline.xml | 422 +++++++++++++++++++++++++++++++++++++++++++++++ tuner/lint-baseline.xml | 213 ++++++++++++++++++++++++ 3 files changed, 650 insertions(+) create mode 100644 common/lint-baseline.xml create mode 100644 lint-baseline.xml create mode 100644 tuner/lint-baseline.xml diff --git a/common/lint-baseline.xml b/common/lint-baseline.xml new file mode 100644 index 00000000..b6f9dfcb --- /dev/null +++ b/common/lint-baseline.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/lint-baseline.xml b/lint-baseline.xml new file mode 100644 index 00000000..d91a1894 --- /dev/null +++ b/lint-baseline.xml @@ -0,0 +1,422 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tuner/lint-baseline.xml b/tuner/lint-baseline.xml new file mode 100644 index 00000000..21efcacb --- /dev/null +++ b/tuner/lint-baseline.xml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3 From 7fa9c010654a5003dfdd2c8a0f76c7c98ccbb700 Mon Sep 17 00:00:00 2001 From: Ulya Trafimovich Date: Mon, 26 Apr 2021 16:35:31 +0100 Subject: Disable dexpreopt and check for tests. Bug: 132357300 Test: treehugger Change-Id: Ie25e3a3f6d5803bea8e55cd5bf4384752e1a0f75 --- tests/common/Android.mk | 4 ++++ tests/input/Android.mk | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/common/Android.mk b/tests/common/Android.mk index b74a65d3..7a232ff7 100644 --- a/tests/common/Android.mk +++ b/tests/common/Android.mk @@ -17,6 +17,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-robolectric-prebuilt \ tv-test-common \ +# Disable dexpreopt and check for test. +LOCAL_ENFORCE_USES_LIBRARIES := false +LOCAL_DEX_PREOPT := false + LOCAL_INSTRUMENTATION_FOR := LiveTv LOCAL_MODULE_TAGS := optional diff --git a/tests/input/Android.mk b/tests/input/Android.mk index c15e4a49..4011dec8 100644 --- a/tests/input/Android.mk +++ b/tests/input/Android.mk @@ -16,6 +16,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ tv-test-common \ tv-common +# Disable dexpreopt and check for test. +LOCAL_ENFORCE_USES_LIBRARIES := false +LOCAL_DEX_PREOPT := false + LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../common/res $(LOCAL_PATH)/res LOCAL_AAPT_FLAGS := --auto-add-overlay \ --extra-packages com.android.tv.testing -- cgit v1.2.3