From d32022ca4ca9c1063db77c0a047550614afb2539 Mon Sep 17 00:00:00 2001 From: Shraddha Basantwani Date: Wed, 11 May 2022 16:14:21 +0530 Subject: Add action check for BootCompletedReceiver Bug: 228314444 Test: Install/Run the PoC app Change-Id: Ia8d6a9d404e36f934b01ef4dcf17a9bcffddec00 --- src/com/android/tv/receiver/BootCompletedReceiver.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/com/android/tv/receiver/BootCompletedReceiver.java b/src/com/android/tv/receiver/BootCompletedReceiver.java index 0eb03bec..0bf6ecf3 100644 --- a/src/com/android/tv/receiver/BootCompletedReceiver.java +++ b/src/com/android/tv/receiver/BootCompletedReceiver.java @@ -56,6 +56,11 @@ public class BootCompletedReceiver extends BroadcastReceiver { Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); return; } + String action = intent.getAction(); + if (!Intent.ACTION_BOOT_COMPLETED.equals(action)) { + Log.w(TAG, "invalid action " + action); + return; + } if (DEBUG) Log.d(TAG, "boot completed " + intent); Starter.start(context); -- cgit v1.2.3 From a3aac6d53e2ed6169b32179026fafbcc494ba0b9 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Thu, 9 Jun 2022 21:44:23 +0000 Subject: SampleTunerTIS add debug for createSession without id Debug message is currently output for onCreateSession only whenever both inputId and sessionId is provided. This change ensures that debug output is created even when only inputId is provided. Test: View output before and after change Change-Id: Ia9972fa6036ccf9017fb4aad3974b9cabb223c90 --- .../tv/samples/sampletunertvinput/SampleTunerTvInputService.java | 3 +++ 1 file changed, 3 insertions(+) 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 03e79650..202519e7 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -89,6 +89,9 @@ public class SampleTunerTvInputService extends TvInputService { @Override public TvInputSessionImpl onCreateSession(String inputId) { + if (DEBUG) { + Log.d(TAG, "onCreateSession(inputId=" + inputId + ")"); + } return new TvInputSessionImpl(this); } -- cgit v1.2.3 From 93252eb7dddba982c928c188a7d22b8c32a24b3e Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Mon, 13 Jun 2022 20:52:41 +0000 Subject: Tuner TIS: Set up SectionFilter This CL creates and opens the sectionFilter to allow SectionEvents to be read by the tuner. Bug: 235869762 Test: Manual testing using Cuttlefish Change-Id: I7b019a055b156e6f0a24680917943babc5e807be --- .../SampleTunerTvInputService.java | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) 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 202519e7..9e8fd9f6 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -14,6 +14,7 @@ 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.SectionSettingsWithSectionBits; import android.media.tv.tuner.filter.TsFilterConfiguration; import android.media.tv.tuner.frontend.AtscFrontendSettings; import android.media.tv.tuner.frontend.DvbtFrontendSettings; @@ -44,6 +45,7 @@ public class SampleTunerTvInputService extends TvInputService { private static final int AUDIO_TPID = 257; private static final int VIDEO_TPID = 256; + private static final int SECTION_TPID = 255; private static final int STATUS_MASK = 0xf; private static final int LOW_THRESHOLD = 0x1000; private static final int HIGH_THRESHOLD = 0x07fff; @@ -103,6 +105,7 @@ public class SampleTunerTvInputService extends TvInputService { private Surface mSurface; private Filter mAudioFilter; private Filter mVideoFilter; + private Filter mSectionFilter; private DvrPlayback mDvr; private Tuner mTuner; private MediaCodec mMediaCodec; @@ -136,6 +139,9 @@ public class SampleTunerTvInputService extends TvInputService { if (mVideoFilter != null) { mVideoFilter.close(); } + if (mSectionFilter != null) { + mSectionFilter.close(); + } if (mDvr != null) { mDvr.close(); mDvr = null; @@ -263,6 +269,41 @@ public class SampleTunerTvInputService extends TvInputService { return videoFilter; } + private Filter sectionFilter() { + Filter sectionFilter = mTuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_SECTION, + FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler), + new FilterCallback() { + @Override + public void onFilterEvent(Filter filter, FilterEvent[] events) { + if (DEBUG) { + Log.d(TAG, "onFilterEvent section, size=" + events.length); + } + for (int i = 0; i < events.length; i++) { + if (DEBUG) { + Log.d(TAG, "events[" + i + "] is " + + events[i].getClass().getSimpleName()); + } + } + } + + @Override + public void onFilterStatusChanged(Filter filter, int status) { + if (DEBUG) { + Log.d(TAG, "onFilterEvent section, status=" + status); + } + } + }); + + SectionSettingsWithSectionBits settings = SectionSettingsWithSectionBits + .builder(Filter.TYPE_TS).build(); + + sectionFilter.configure( + TsFilterConfiguration.builder().setTpid(SECTION_TPID) + .setSettings(settings).build()); + + return sectionFilter; + } + private DvrPlayback dvrPlayback() { DvrPlayback dvr = mTuner.openDvrPlayback(DVR_BUFFER_SIZE, new HandlerExecutor(mHandler), status -> { @@ -352,8 +393,10 @@ public class SampleTunerTvInputService extends TvInputService { mAudioFilter = audioFilter(); mVideoFilter = videoFilter(); + mSectionFilter = sectionFilter(); mAudioFilter.start(); mVideoFilter.start(); + mSectionFilter.start(); // use dvr playback to feed the data on platform without physical tuner mDvr = dvrPlayback(); tune(); -- cgit v1.2.3 From 5d2bd3b14c12ae728d6d303f27fb32fb0bc8546a Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Fri, 24 Jun 2022 23:46:47 +0000 Subject: SampleTIS add dvrPlayback to SetupActivity Currently, the SetupActivity for the SampleTIS uses only hardcoded values for its channel information. Since we would like to instead take this data from our Hardware Access Layer before the TIS is set up, some components which communicate with the HAL must be replicated into our SetupActivity. The newly added Utils class is used to avoid code repetition. Bug: 235869762 Test: Run manually on Cuttlefish, with same output Test: TIS as before changes. Change-Id: I4c55a7b384e5a70fb30dc6d0c749349ad4705aa1 --- .../SampleTunerTvInputService.java | 48 +------------- .../SampleTunerTvInputSetupActivity.java | 34 ++++++++++ .../SampleTunerTvInputUtils.java | 74 ++++++++++++++++++++++ 3 files changed, 111 insertions(+), 45 deletions(-) create mode 100644 tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java 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 9e8fd9f6..5480accd 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -8,7 +8,6 @@ import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.LinearBlock; 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; @@ -16,20 +15,16 @@ import android.media.tv.tuner.filter.FilterEvent; import android.media.tv.tuner.filter.MediaEvent; import android.media.tv.tuner.filter.SectionSettingsWithSectionBits; 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; + import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayDeque; @@ -46,14 +41,9 @@ public class SampleTunerTvInputService extends TvInputService { private static final int AUDIO_TPID = 257; private static final int VIDEO_TPID = 256; private static final int SECTION_TPID = 255; - 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 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 = false; @@ -304,39 +294,6 @@ public class SampleTunerTvInputService extends TvInputService { return sectionFilter; } - private DvrPlayback dvrPlayback() { - DvrPlayback dvr = mTuner.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); - } - String testFile = mContext.getFilesDir().getAbsolutePath() + "/" + ES_FILE_NAME; - File file = new File(testFile); - 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) @@ -398,7 +355,8 @@ public class SampleTunerTvInputService extends TvInputService { mVideoFilter.start(); mSectionFilter.start(); // use dvr playback to feed the data on platform without physical tuner - mDvr = dvrPlayback(); + mDvr = SampleTunerTvInputUtils.createDvrPlayback(mTuner, mHandler, + mContext, ES_FILE_NAME); tune(); mDvr.start(); mMediaCodec.start(); diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java index b932b605..620d90c1 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java @@ -3,7 +3,13 @@ package com.android.tv.samples.sampletunertvinput; import android.app.Activity; import android.content.Intent; import android.media.tv.TvInputInfo; +import android.media.tv.TvInputService; +import android.media.tv.tuner.Tuner; +import android.media.tv.tuner.dvr.DvrPlayback; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; + import com.android.tv.testing.data.ChannelInfo; import com.android.tv.testing.data.ChannelUtils; import com.android.tv.testing.data.ProgramInfo; @@ -11,6 +17,11 @@ import java.util.Collections; /** Setup activity for SampleTunerTvInput */ public class SampleTunerTvInputSetupActivity extends Activity { + private static final String ES_FILE_NAME = "test.es"; + + private Tuner mTuner; + private DvrPlayback mDvr; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -43,8 +54,31 @@ public class SampleTunerTvInputSetupActivity extends Activity { Intent intent = getIntent(); String inputId = intent.getStringExtra(TvInputInfo.EXTRA_INPUT_ID); ChannelUtils.updateChannels(this, inputId, Collections.singletonList(channel)); + + initTuner(); + setResult(Activity.RESULT_OK); finish(); } + @Override + public void onDestroy() { + if (mTuner != null) { + mTuner.close(); + mTuner = null; + } + if (mDvr != null) { + mDvr.close(); + mDvr = null; + } + } + + private void initTuner() { + mTuner = new Tuner(getApplicationContext(), null, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); + Handler handler = new Handler(Looper.myLooper()); + mDvr = SampleTunerTvInputUtils.createDvrPlayback(mTuner, handler, + getApplicationContext(), ES_FILE_NAME); + } + } diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java new file mode 100644 index 00000000..b392e323 --- /dev/null +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 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.samples.sampletunertvinput; + +import android.content.Context; +import android.media.tv.tuner.Tuner; +import android.media.tv.tuner.dvr.DvrPlayback; +import android.media.tv.tuner.dvr.DvrSettings; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; + +public class SampleTunerTvInputUtils { + private static final String TAG = "SampleTunerTvInput"; + private static final boolean DEBUG = true; + + 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 DVR_BUFFER_SIZE = 4000000; + private static final int PACKET_SIZE = 188; + + public static DvrPlayback createDvrPlayback(Tuner tuner, Handler handler, + Context context, String fileName) { + DvrPlayback dvr = tuner.openDvrPlayback(DVR_BUFFER_SIZE, new HandlerExecutor(handler), + 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); + } + String testFile = context.getFilesDir().getAbsolutePath() + "/" + fileName; + File file = new File(testFile); + 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; + } +} -- cgit v1.2.3 From f5b796194c4dc7f5007a5075b5f2b7c3c31b725f Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Fri, 24 Jun 2022 23:51:21 +0000 Subject: SampleTIS add FeSettings and tune to SetupActivity Currently, the SetupActivity for the SampleTIS uses only hardcoded values for its channel information. Since we would like to instead take this data from our Hardware Access Layer before the TIS is set up, some components which communicate with the HAL must be replicated into our SetupActivity. The Utils class is used to avoid code repetition. Bug: 235869762 Test: Manually on cuttlefish. See output from tuner and Test: dvr on SetupActivity while TIS remains unchanged. Change-Id: Ib020e2b6e47b5bf686391fbd23c38fb88387d01f --- .../SampleTunerTvInputService.java | 32 +--------------------- .../SampleTunerTvInputSetupActivity.java | 1 + .../SampleTunerTvInputUtils.java | 30 ++++++++++++++++++++ 3 files changed, 32 insertions(+), 31 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 5480accd..b3463ee8 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -41,9 +41,7 @@ public class SampleTunerTvInputService extends TvInputService { private static final int AUDIO_TPID = 257; private static final int VIDEO_TPID = 256; private static final int SECTION_TPID = 255; - private static final int FREQUENCY = 578000; private static final int FILTER_BUFFER_SIZE = 16000000; - private static final int INPUT_FILE_MAX_SIZE = 700000; private static final int TIMEOUT_US = 100000; private static final boolean SAVE_DATA = false; @@ -294,34 +292,6 @@ public class SampleTunerTvInputService extends TvInputService { return sectionFilter; } - 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(); - mTuner.setOnTuneEventListener(new HandlerExecutor(mHandler), new OnTuneEventListener() { - @Override - public void onTuneEvent(int tuneEvent) { - if (DEBUG) { - Log.d(TAG, "onTuneEvent " + tuneEvent); - } - long read = mDvr.read(INPUT_FILE_MAX_SIZE); - if (DEBUG) { - Log.d(TAG, "read=" + read); - } - } - }); - mTuner.tune(feSettings); - } - private boolean initCodec() { if (mMediaCodec != null) { mMediaCodec.release(); @@ -357,7 +327,7 @@ public class SampleTunerTvInputService extends TvInputService { // use dvr playback to feed the data on platform without physical tuner mDvr = SampleTunerTvInputUtils.createDvrPlayback(mTuner, mHandler, mContext, ES_FILE_NAME); - tune(); + SampleTunerTvInputUtils.tune(mTuner, mHandler, mDvr); mDvr.start(); mMediaCodec.start(); diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java index 620d90c1..0cda0072 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java @@ -79,6 +79,7 @@ public class SampleTunerTvInputSetupActivity extends Activity { Handler handler = new Handler(Looper.myLooper()); mDvr = SampleTunerTvInputUtils.createDvrPlayback(mTuner, handler, getApplicationContext(), ES_FILE_NAME); + SampleTunerTvInputUtils.tune(mTuner, handler, mDvr); } } diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java index b392e323..9e5cc41b 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java @@ -20,6 +20,7 @@ import android.content.Context; import android.media.tv.tuner.Tuner; import android.media.tv.tuner.dvr.DvrPlayback; import android.media.tv.tuner.dvr.DvrSettings; +import android.media.tv.tuner.frontend.DvbtFrontendSettings; import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelFileDescriptor; @@ -37,6 +38,8 @@ public class SampleTunerTvInputUtils { private static final int HIGH_THRESHOLD = 0x07fff; private static final int DVR_BUFFER_SIZE = 4000000; private static final int PACKET_SIZE = 188; + private static final long FREQUENCY = 578000; + private static final int INPUT_FILE_MAX_SIZE = 700000; public static DvrPlayback createDvrPlayback(Tuner tuner, Handler handler, Context context, String fileName) { @@ -71,4 +74,31 @@ public class SampleTunerTvInputUtils { } return dvr; } + + public static void tune(Tuner tuner, Handler handler, DvrPlayback dvr) { + DvbtFrontendSettings feSettings = DvbtFrontendSettings.builder() + .setFrequencyLong(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(handler), 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); + } } -- cgit v1.2.3 From ba15557d41b7997e66cf014dd5032fadcebf30f4 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Sat, 25 Jun 2022 00:08:13 +0000 Subject: SampleTIS move filter creation to Utils To allow SectionEvents to be read in the SetupActivity, SectionFilter creation should be moved into our Utils class. Audio and Video Filter creation, as well as our Default Logging Listener which is currently used by 3 Filters, is also moved into Utils to maintain consistency and decrease repetition. Bug: 235869762 Test: Manual on cuttlefish. New SectionFilter receives Test: events while SectionTIS exhibits same behavior. Change-Id: I7e7ab4689b712e599f62b583415102781536059b --- .../SampleTunerTvInputService.java | 151 +++++---------------- .../SampleTunerTvInputSetupActivity.java | 12 ++ .../SampleTunerTvInputUtils.java | 67 +++++++++ 3 files changed, 113 insertions(+), 117 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 b3463ee8..e4d7b4b4 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -8,20 +8,14 @@ import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.LinearBlock; import android.media.MediaFormat; import android.media.tv.tuner.dvr.DvrPlayback; -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.SectionSettingsWithSectionBits; -import android.media.tv.tuner.filter.TsFilterConfiguration; -import android.media.tv.tuner.frontend.DvbtFrontendSettings; -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.util.Log; import android.view.Surface; @@ -38,11 +32,6 @@ 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 SECTION_TPID = 255; - private static final int FILTER_BUFFER_SIZE = 16000000; - private static final int TIMEOUT_US = 100000; private static final boolean SAVE_DATA = false; private static final String ES_FILE_NAME = "test.es"; @@ -183,113 +172,38 @@ public class SampleTunerTvInputService extends TvInputService { } } - private Filter audioFilter() { - 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++) { - if (DEBUG) { - 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 = 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++) { - if (DEBUG) { - Log.d(TAG, "events[" + i + "] is " - + events[i].getClass().getSimpleName()); - } - if (events[i] instanceof MediaEvent) { - MediaEvent me = (MediaEvent) events[i]; - mDataQueue.add(me); - if (SAVE_DATA) { - mSavedData.add(me); - } - } - } - } - - @Override - public void onFilterStatusChanged(Filter filter, int status) { - if (DEBUG) { - Log.d(TAG, "onFilterEvent video, status=" + status); - } - if (status == Filter.STATUS_DATA_READY) { - mDataReady = true; - } - } - }); - AvSettings settings = - AvSettings.builder(Filter.TYPE_TS, false).setPassthrough(false).build(); - videoFilter.configure( - TsFilterConfiguration.builder().setTpid(VIDEO_TPID) - .setSettings(settings).build()); - return videoFilter; - } - - private Filter sectionFilter() { - Filter sectionFilter = mTuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_SECTION, - FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler), - new FilterCallback() { - @Override - public void onFilterEvent(Filter filter, FilterEvent[] events) { - if (DEBUG) { - Log.d(TAG, "onFilterEvent section, size=" + events.length); - } - for (int i = 0; i < events.length; i++) { - if (DEBUG) { - Log.d(TAG, "events[" + i + "] is " - + events[i].getClass().getSimpleName()); - } - } + private FilterCallback videoFilterCallback() { + return 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++) { + if (DEBUG) { + Log.d(TAG, "events[" + i + "] is " + + events[i].getClass().getSimpleName()); } - - @Override - public void onFilterStatusChanged(Filter filter, int status) { - if (DEBUG) { - Log.d(TAG, "onFilterEvent section, status=" + status); + if (events[i] instanceof MediaEvent) { + MediaEvent me = (MediaEvent) events[i]; + mDataQueue.add(me); + if (SAVE_DATA) { + mSavedData.add(me); } } - }); - - SectionSettingsWithSectionBits settings = SectionSettingsWithSectionBits - .builder(Filter.TYPE_TS).build(); - - sectionFilter.configure( - TsFilterConfiguration.builder().setTpid(SECTION_TPID) - .setSettings(settings).build()); + } + } - return sectionFilter; + @Override + public void onFilterStatusChanged(Filter filter, int status) { + if (DEBUG) { + Log.d(TAG, "onFilterEvent video, status=" + status); + } + if (status == Filter.STATUS_DATA_READY) { + mDataReady = true; + } + } + }; } private boolean initCodec() { @@ -318,9 +232,12 @@ public class SampleTunerTvInputService extends TvInputService { mTuner = new Tuner(mContext, mSessionId, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); - mAudioFilter = audioFilter(); - mVideoFilter = videoFilter(); - mSectionFilter = sectionFilter(); + mAudioFilter = SampleTunerTvInputUtils.createAvFilter(mTuner, mHandler, + SampleTunerTvInputUtils.createDefaultLoggingFilterCallback("audio"), true); + mVideoFilter = SampleTunerTvInputUtils.createAvFilter(mTuner, mHandler, + videoFilterCallback(), false); + mSectionFilter = SampleTunerTvInputUtils.createSectionFilter(mTuner, mHandler, + SampleTunerTvInputUtils.createDefaultLoggingFilterCallback("section")); mAudioFilter.start(); mVideoFilter.start(); mSectionFilter.start(); diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java index 0cda0072..5744510c 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java @@ -6,6 +6,7 @@ import android.media.tv.TvInputInfo; import android.media.tv.TvInputService; import android.media.tv.tuner.Tuner; import android.media.tv.tuner.dvr.DvrPlayback; +import android.media.tv.tuner.filter.Filter; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -21,6 +22,7 @@ public class SampleTunerTvInputSetupActivity extends Activity { private Tuner mTuner; private DvrPlayback mDvr; + private Filter mSectionFilter; @Override public void onCreate(Bundle savedInstanceState) { @@ -71,15 +73,25 @@ public class SampleTunerTvInputSetupActivity extends Activity { mDvr.close(); mDvr = null; } + if (mSectionFilter != null) { + mSectionFilter.close(); + mSectionFilter = null; + } } private void initTuner() { mTuner = new Tuner(getApplicationContext(), null, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); Handler handler = new Handler(Looper.myLooper()); + + mSectionFilter = SampleTunerTvInputUtils.createSectionFilter(mTuner, handler, + SampleTunerTvInputUtils.createDefaultLoggingFilterCallback("section")); + mSectionFilter.start(); + mDvr = SampleTunerTvInputUtils.createDvrPlayback(mTuner, handler, getApplicationContext(), ES_FILE_NAME); SampleTunerTvInputUtils.tune(mTuner, handler, mDvr); + mDvr.start(); } } diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java index 9e5cc41b..0287abf4 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java @@ -20,6 +20,12 @@ import android.content.Context; import android.media.tv.tuner.Tuner; 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.SectionSettingsWithSectionBits; +import android.media.tv.tuner.filter.TsFilterConfiguration; import android.media.tv.tuner.frontend.DvbtFrontendSettings; import android.os.Handler; import android.os.HandlerExecutor; @@ -33,6 +39,11 @@ public class SampleTunerTvInputUtils { 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 SECTION_TPID = 255; + private static final int FILTER_BUFFER_SIZE = 16000000; + private static final int STATUS_MASK = 0xf; private static final int LOW_THRESHOLD = 0x1000; private static final int HIGH_THRESHOLD = 0x07fff; @@ -101,4 +112,60 @@ public class SampleTunerTvInputUtils { tuner.tune(feSettings); } + + public static Filter createSectionFilter(Tuner tuner, Handler handler, + FilterCallback callback) { + Filter sectionFilter = tuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_SECTION, + FILTER_BUFFER_SIZE, new HandlerExecutor(handler), callback); + + SectionSettingsWithSectionBits settings = SectionSettingsWithSectionBits + .builder(Filter.TYPE_TS).build(); + + sectionFilter.configure( + TsFilterConfiguration.builder().setTpid(SECTION_TPID) + .setSettings(settings).build()); + + return sectionFilter; + } + + public static Filter createAvFilter(Tuner tuner, Handler handler, + FilterCallback callback, boolean isAudio) { + Filter avFilter = tuner.openFilter(Filter.TYPE_TS, + isAudio ? Filter.SUBTYPE_AUDIO : Filter.SUBTYPE_VIDEO, + FILTER_BUFFER_SIZE, + new HandlerExecutor(handler), + callback); + + AvSettings settings = + AvSettings.builder(Filter.TYPE_TS, isAudio).setPassthrough(false).build(); + avFilter.configure( + TsFilterConfiguration.builder(). + setTpid(isAudio ? AUDIO_TPID : VIDEO_TPID) + .setSettings(settings).build()); + return avFilter; + } + + public static FilterCallback createDefaultLoggingFilterCallback(String filterType) { + return new FilterCallback() { + @Override + public void onFilterEvent(Filter filter, FilterEvent[] events) { + if (DEBUG) { + Log.d(TAG, "onFilterEvent " + filterType + ", size=" + events.length); + } + for (int i = 0; i < events.length; i++) { + if (DEBUG) { + Log.d(TAG, "events[" + i + "] is " + + events[i].getClass().getSimpleName()); + } + } + } + + @Override + public void onFilterStatusChanged(Filter filter, int status) { + if (DEBUG) { + Log.d(TAG, "onFilterStatusChanged " + filterType + ", status=" + status); + } + } + }; + } } -- cgit v1.2.3 From 1e9805c9ff48ad864cc1c0b56eb21f64646c87e1 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Mon, 27 Jun 2022 21:36:49 +0000 Subject: SampleTunerTIS setup read SectionEvent data In the SetupActivity, a sectionFilter is created in order to receive SectionEvents which can provide us channel and network data. In this CL, SetupActivity is modified to remain open until it receives data from a SectionEvent. Currently it only expects a single test value which is sets as its channel number. Bug: 237323181 Test: Run manually on Cuttlefish using new test.es Test: containing section data. Should see channel number Test: matching input file, with no other functionality changes. Change-Id: Icca8c0dc605fc83df00ad99e65f687fd477a646d --- .../SampleTunerTvInputService.java | 3 +- .../SampleTunerTvInputSetupActivity.java | 124 +++++++++++++++------ .../SampleTunerTvInputUtils.java | 4 +- 3 files changed, 92 insertions(+), 39 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 e4d7b4b4..a995e51a 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -8,6 +8,7 @@ import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.LinearBlock; import android.media.MediaFormat; import android.media.tv.tuner.dvr.DvrPlayback; +import android.media.tv.tuner.dvr.DvrSettings; import android.media.tv.tuner.filter.Filter; import android.media.tv.tuner.filter.FilterCallback; import android.media.tv.tuner.filter.FilterEvent; @@ -243,7 +244,7 @@ public class SampleTunerTvInputService extends TvInputService { mSectionFilter.start(); // use dvr playback to feed the data on platform without physical tuner mDvr = SampleTunerTvInputUtils.createDvrPlayback(mTuner, mHandler, - mContext, ES_FILE_NAME); + mContext, ES_FILE_NAME, DvrSettings.DATA_FORMAT_ES); SampleTunerTvInputUtils.tune(mTuner, mHandler, mDvr); mDvr.start(); mMediaCodec.start(); diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java index 5744510c..a7013164 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java @@ -6,19 +6,30 @@ import android.media.tv.TvInputInfo; import android.media.tv.TvInputService; import android.media.tv.tuner.Tuner; import android.media.tv.tuner.dvr.DvrPlayback; +import android.media.tv.tuner.dvr.DvrSettings; 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.SectionEvent; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.util.Log; import com.android.tv.testing.data.ChannelInfo; import com.android.tv.testing.data.ChannelUtils; import com.android.tv.testing.data.ProgramInfo; + +import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.Collections; /** Setup activity for SampleTunerTvInput */ public class SampleTunerTvInputSetupActivity extends Activity { - private static final String ES_FILE_NAME = "test.es"; + private static final String TAG = "SampleTunerTvInput"; + private static final boolean DEBUG = true; + + private static final String ES_FILE_NAME = "test.ts"; private Tuner mTuner; private DvrPlayback mDvr; @@ -27,40 +38,7 @@ public class SampleTunerTvInputSetupActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ChannelInfo channel = - new ChannelInfo.Builder() - .setNumber("1-1") - .setName("Sample Channel") - .setLogoUrl( - ChannelInfo.getUriStringForChannelLogo(this, 100)) - .setOriginalNetworkId(1) - .setVideoWidth(640) - .setVideoHeight(480) - .setAudioChannel(2) - .setAudioLanguageCount(1) - .setHasClosedCaption(false) - .setProgram( - new ProgramInfo( - "Sample Program", - "", - 0, - 0, - ProgramInfo.GEN_POSTER, - "Sample description", - ProgramInfo.GEN_DURATION, - null, - ProgramInfo.GEN_GENRE, - null)) - .build(); - - Intent intent = getIntent(); - String inputId = intent.getStringExtra(TvInputInfo.EXTRA_INPUT_ID); - ChannelUtils.updateChannels(this, inputId, Collections.singletonList(channel)); - initTuner(); - - setResult(Activity.RESULT_OK); - finish(); } @Override @@ -79,17 +57,91 @@ public class SampleTunerTvInputSetupActivity extends Activity { } } + private void setChannel(byte[] sectionData) { + // Currently reading single value directly as a test + String channelNumber = new String(Arrays.copyOfRange(sectionData, 3, 6)); + + ChannelInfo channel = + new ChannelInfo.Builder() + .setNumber(channelNumber) + .setName("Sample Channel") + .setLogoUrl( + ChannelInfo.getUriStringForChannelLogo(this, 100)) + .setOriginalNetworkId(1) + .setVideoWidth(640) + .setVideoHeight(480) + .setAudioChannel(2) + .setAudioLanguageCount(1) + .setHasClosedCaption(false) + .setProgram( + new ProgramInfo( + "Sample Program", + "", + 0, + 0, + ProgramInfo.GEN_POSTER, + "Sample description", + ProgramInfo.GEN_DURATION, + null, + ProgramInfo.GEN_GENRE, + null)) + .build(); + + Intent intent = getIntent(); + String inputId = intent.getStringExtra(TvInputInfo.EXTRA_INPUT_ID); + ChannelUtils.updateChannels(this, inputId, Collections.singletonList(channel)); + + setResult(Activity.RESULT_OK); + finish(); + } + + private FilterCallback sectionFilterCallback() { + return new FilterCallback() { + @Override + public void onFilterEvent(Filter filter, FilterEvent[] events) { + if (DEBUG) { + Log.d(TAG, "onFilterEvent setup section, size=" + events.length); + } + for (int i = 0; i < events.length; i++) { + if (DEBUG) { + Log.d(TAG, "events[" + i + "] is " + + events[i].getClass().getSimpleName()); + } + if (events[i] instanceof SectionEvent) { + SectionEvent sectionEvent = (SectionEvent) events[i]; + int dataSize = (int)sectionEvent.getDataLengthLong(); + if (DEBUG) { + Log.d(TAG, "section dataSize:" + dataSize); + } + + byte[] data = new byte[dataSize]; + filter.read(data, 0, dataSize); + + setChannel(data); + } + } + } + + @Override + public void onFilterStatusChanged(Filter filter, int status) { + if (DEBUG) { + Log.d(TAG, "onFilterStatusChanged setup section, status=" + status); + } + } + }; + } + private void initTuner() { mTuner = new Tuner(getApplicationContext(), null, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); Handler handler = new Handler(Looper.myLooper()); mSectionFilter = SampleTunerTvInputUtils.createSectionFilter(mTuner, handler, - SampleTunerTvInputUtils.createDefaultLoggingFilterCallback("section")); + sectionFilterCallback()); mSectionFilter.start(); mDvr = SampleTunerTvInputUtils.createDvrPlayback(mTuner, handler, - getApplicationContext(), ES_FILE_NAME); + getApplicationContext(), ES_FILE_NAME, DvrSettings.DATA_FORMAT_TS); SampleTunerTvInputUtils.tune(mTuner, handler, mDvr); mDvr.start(); } diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java index 0287abf4..bcbfd7c3 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java @@ -53,7 +53,7 @@ public class SampleTunerTvInputUtils { private static final int INPUT_FILE_MAX_SIZE = 700000; public static DvrPlayback createDvrPlayback(Tuner tuner, Handler handler, - Context context, String fileName) { + Context context, String fileName, int dataFormat) { DvrPlayback dvr = tuner.openDvrPlayback(DVR_BUFFER_SIZE, new HandlerExecutor(handler), status -> { if (DEBUG) { @@ -65,7 +65,7 @@ public class SampleTunerTvInputUtils { .setStatusMask(STATUS_MASK) .setLowThreshold(LOW_THRESHOLD) .setHighThreshold(HIGH_THRESHOLD) - .setDataFormat(DvrSettings.DATA_FORMAT_ES) + .setDataFormat(dataFormat) .setPacketSize(PACKET_SIZE) .build()); if (DEBUG) { -- cgit v1.2.3 From 214d42a5a97b3fe43fe1348c5cae0d4515b2bced Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 29 Jun 2022 01:42:54 +0000 Subject: SampleTunerTIS Add SectionParser for VCT The SetupActivity for the SampleTunerTIS is now receiving SectionEvents. This CL adds a SectionParser in order to process these events, as well as the method and data structure which will be needed for the specific VCT event which we currently expect. Bug: 237323181 Test: Run manually on Cuttlefish. Should see a received event, and see the default channel number and name being received from the parsing method. Change-Id: I8dbd0b0535d9caa3d685aa9b2865d954315d4cd9 --- .../SampleTunerTvInputSectionParser.java | 74 ++++++++++++++++++++++ .../SampleTunerTvInputSetupActivity.java | 20 ++++-- 2 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java new file mode 100644 index 00000000..c883f425 --- /dev/null +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 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.samples.sampletunertvinput; + +import android.util.Log; + +import java.util.Locale; + +/** Parser for ATSC PSIP sections */ +public class SampleTunerTvInputSectionParser { + + /** + * Parses a single TVCT section, as defined in A/65 6.4 + * @param data, a ByteBuffer containing a single TVCT section which describes only one channel + * @return null if there is an error while parsing, the channel with parsed data otherwise + */ + public static TvctChannelInfo parseTvctSection(byte[] data) { + // TODO: Parse the data for channel information + return new TvctChannelInfo("Sample Channel", 1, 1); + } + + // Contains the portion of the data contained in the TVCT used by + // our SampleTunerTvInputSetupActivity + public static class TvctChannelInfo { + private final String mChannelName; + private final int mMajorChannelNumber; + private final int mMinorChannelNumber; + + public TvctChannelInfo( + String channelName, + int majorChannelNumber, + int minorChannelNumber) { + mChannelName = channelName; + mMajorChannelNumber = majorChannelNumber; + mMinorChannelNumber = minorChannelNumber; + } + + public String getChannelName() { + return mChannelName; + } + + public int getMajorChannelNumber() { + return mMajorChannelNumber; + } + + public int getMinorChannelNumber() { + return mMinorChannelNumber; + } + + @Override + public String toString() { + return String.format( + Locale.US, + "ChannelName: %s ChannelNumber: %d-%d", + mChannelName, + mMajorChannelNumber, + mMinorChannelNumber); + } + } +} diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java index a7013164..7933ccc6 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java @@ -20,9 +20,8 @@ import com.android.tv.testing.data.ChannelInfo; import com.android.tv.testing.data.ChannelUtils; import com.android.tv.testing.data.ProgramInfo; -import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.Collections; +import java.util.Locale; /** Setup activity for SampleTunerTvInput */ public class SampleTunerTvInputSetupActivity extends Activity { @@ -58,13 +57,24 @@ public class SampleTunerTvInputSetupActivity extends Activity { } private void setChannel(byte[] sectionData) { - // Currently reading single value directly as a test - String channelNumber = new String(Arrays.copyOfRange(sectionData, 3, 6)); + SampleTunerTvInputSectionParser.TvctChannelInfo channelInfo = + SampleTunerTvInputSectionParser.parseTvctSection(sectionData); + + String channelNumber = ""; + String channelName = ""; + + if(channelInfo == null) { + Log.e(TAG, "Did not receive channel description from parser"); + } else { + channelNumber = String.format(Locale.US, "%d-%d", channelInfo.getMajorChannelNumber(), + channelInfo.getMinorChannelNumber()); + channelName = channelInfo.getChannelName(); + } ChannelInfo channel = new ChannelInfo.Builder() .setNumber(channelNumber) - .setName("Sample Channel") + .setName(channelName) .setLogoUrl( ChannelInfo.getUriStringForChannelLogo(this, 100)) .setOriginalNetworkId(1) -- cgit v1.2.3 From 792fde7b15cf01eaae2e95fad051d0561355a033 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 29 Jun 2022 01:55:09 +0000 Subject: SampleTunerTIS SectionParser add PSIP check This CL adds functionality to our SectionParser which checks whether the incoming data is a valid PSIP section, including validating its CRC32/MPEG-2 sum. Bug: 237323181 Test: Manually this Cuttlefish. Should see invalid sections or section without correct CRC32 rejected. Change-Id: I151404a23baacbf47e28f7de0eac4ea6d079deb2 --- .../SampleTunerTvInputSectionParser.java | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java index c883f425..a983d1f4 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java @@ -22,6 +22,7 @@ import java.util.Locale; /** Parser for ATSC PSIP sections */ public class SampleTunerTvInputSectionParser { + private static final String TAG = "SampleTunerTvInput"; /** * Parses a single TVCT section, as defined in A/65 6.4 @@ -29,10 +30,38 @@ public class SampleTunerTvInputSectionParser { * @return null if there is an error while parsing, the channel with parsed data otherwise */ public static TvctChannelInfo parseTvctSection(byte[] data) { + if (!checkValidPsipSection(data)) { + return null; + } // TODO: Parse the data for channel information return new TvctChannelInfo("Sample Channel", 1, 1); } + private static boolean checkValidPsipSection(byte[] data) { + if (data.length < 13) { + Log.e(TAG, "Section was too small"); + return false; + } + if ((data[0] & 0xff) == 0xff) { + // Should clear stuffing bytes as detailed by H222.0 section 2.4.4. + Log.e(TAG, "Unexpected stuffing bytes while parsing section"); + return false; + } + int sectionLength = (((data[1] & 0x0f) << 8) | (data[2] & 0xff)) + 3; + if (sectionLength != data.length) { + Log.e(TAG, "Length mismatch while parsing section"); + return false; + } + int sectionNumber = data[6] & 0xff; + int lastSectionNumber = data[7] & 0xff; + if(sectionNumber > lastSectionNumber) { + Log.e(TAG, "Found sectionNumber > lastSectionNumber while parsing section"); + return false; + } + // TODO: Check CRC 32/MPEG for validity + return true; + } + // Contains the portion of the data contained in the TVCT used by // our SampleTunerTvInputSetupActivity public static class TvctChannelInfo { -- cgit v1.2.3 From 64163296949fb5891b29dee5858e5b6be6e39a86 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 29 Jun 2022 17:25:55 +0000 Subject: SampleTunerTIS Provide basic data to SetupActivity This CL adds the basic data parsing of VCT tables into the SectionParser. Further commits will allow reading of the ExtendedChannelName from the descriptors, in addition to the shortName which is currently being read directly from the VCT data. Bug: 237323181 Test: Run manually on cuttlefish. Should see the correct shortName and channel name from input TS. Change-Id: I36e9bb2209bba065d00089363256e58381d1ca1d --- .../SampleTunerTvInputSectionParser.java | 37 ++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java index a983d1f4..35ae278f 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java @@ -18,11 +18,14 @@ package com.android.tv.samples.sampletunertvinput; import android.util.Log; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Locale; /** Parser for ATSC PSIP sections */ public class SampleTunerTvInputSectionParser { private static final String TAG = "SampleTunerTvInput"; + private static final boolean DEBUG = true; /** * Parses a single TVCT section, as defined in A/65 6.4 @@ -33,8 +36,38 @@ public class SampleTunerTvInputSectionParser { if (!checkValidPsipSection(data)) { return null; } - // TODO: Parse the data for channel information - return new TvctChannelInfo("Sample Channel", 1, 1); + int numChannels = data[9] & 0xff; + if(numChannels != 1) { + Log.e(TAG, "parseTVCTSection expected 1 channel, found " + numChannels); + return null; + } + // TVCT Sections are a minimum of 16 bytes, with a minimum of 32 bytes per channel + if(data.length < 48) { + Log.e(TAG, "parseTVCTSection found section under minimum length"); + return null; + } + + // shortName begins at data[10] and ends at either the first stuffing + // UTF-16 character of value 0x0000, or at a length of 14 Bytes + int shortNameLength = 14; + for(int i = 0; i < 14; i += 2) { + int charValue = ((data[10 + i] & 0xff) << 8) | (data[10 + (i + 1)] & 0xff); + if (charValue == 0x0000) { + shortNameLength = i; + break; + } + } + // Data field positions are as defined by A/65 Section 6.4 for one channel + String shortName = new String(Arrays.copyOfRange(data, 10, 10 + shortNameLength), + StandardCharsets.UTF_16); + int majorNumber = ((data[24] & 0x0f) << 6) | ((data[25] & 0xff) >> 2); + int minorNumber = ((data[25] & 0x03) << 8) | (data[26] & 0xff); + if (DEBUG) { + Log.d(TAG, "parseTVCTSection found shortName: " + shortName + + " channel number: " + majorNumber + "-" + minorNumber); + } + + return new TvctChannelInfo(shortName, majorNumber, minorNumber); } private static boolean checkValidPsipSection(byte[] data) { -- cgit v1.2.3 From bf45a704804e8c7f8b998c1cd075342d54731843 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 29 Jun 2022 17:52:37 +0000 Subject: SampleTunerTIS add descriptor parsing When we are parsing the TVCT table in our SectionParser, we require some data which is located within descriptors. This CL adds a framework for parsing descriptors, which we will utilize in future CLs to extract the ExtendedChannelName for our SetupActivity. Bug: 237323181 Test: Run manually with cuttlefish. Should see the channel name "Sample" if an ExtendedChannelNameDescriptor exists, and the shortName located in the TVCT otherwise. Change-Id: Ife8418642169d0de7cca85cf679a30a452c69716 --- .../SampleTunerTvInputSectionParser.java | 85 +++++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java index 35ae278f..821597d1 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java @@ -19,7 +19,9 @@ package com.android.tv.samples.sampletunertvinput; import android.util.Log; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Locale; /** Parser for ATSC PSIP sections */ @@ -27,6 +29,8 @@ public class SampleTunerTvInputSectionParser { private static final String TAG = "SampleTunerTvInput"; private static final boolean DEBUG = true; + public static final byte DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME = (byte) 0xa0; + /** * Parses a single TVCT section, as defined in A/65 6.4 * @param data, a ByteBuffer containing a single TVCT section which describes only one channel @@ -58,16 +62,66 @@ public class SampleTunerTvInputSectionParser { } } // Data field positions are as defined by A/65 Section 6.4 for one channel - String shortName = new String(Arrays.copyOfRange(data, 10, 10 + shortNameLength), + String name = new String(Arrays.copyOfRange(data, 10, 10 + shortNameLength), StandardCharsets.UTF_16); int majorNumber = ((data[24] & 0x0f) << 6) | ((data[25] & 0xff) >> 2); int minorNumber = ((data[25] & 0x03) << 8) | (data[26] & 0xff); if (DEBUG) { - Log.d(TAG, "parseTVCTSection found shortName: " + shortName + Log.d(TAG, "parseTVCTSection found shortName: " + name + " channel number: " + majorNumber + "-" + minorNumber); } + int descriptorsLength = ((data[40] & 0x03) << 8) | (data[41] & 0xff); + List descriptors = parseDescriptors(data, 42, 42 + descriptorsLength); + for (TsDescriptor descriptor : descriptors) { + if (descriptor instanceof ExtendedChannelNameDescriptor) { + ExtendedChannelNameDescriptor longNameDescriptor = + (ExtendedChannelNameDescriptor)descriptor; + name = longNameDescriptor.getLongChannelName(); + } + } + + return new TvctChannelInfo(name, majorNumber, minorNumber); + } + + // Descriptor data structure defined in ISO/IEC 13818-1 Section 2.6 + // Returns an empty list on parsing failures + private static List parseDescriptors(byte[] data, int offset, int limit) { + List descriptors = new ArrayList<>(); + if (data.length < limit) { + Log.e(TAG, "parseDescriptors given limit larger than data"); + return descriptors; + } + int pos = offset; + while (pos + 1 < limit) { + int tag = data[pos] & 0xff; + int length = data[pos + 1] & 0xff; + if (length <= 0) { + continue; + } + pos += 2; - return new TvctChannelInfo(shortName, majorNumber, minorNumber); + if (limit < pos + length) { + Log.e(TAG, "parseDescriptors found descriptor with length longer than limit"); + break; + } + if (DEBUG) { + Log.d(TAG, "parseDescriptors found descriptor with tag: " + tag); + } + TsDescriptor descriptor = null; + switch ((byte) tag) { + case DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME: + // TODO: Parse ExtendedChannelNameDescriptor from data + descriptor = new ExtendedChannelNameDescriptor("Sample"); + break; + default: + break; + } + if (descriptor != null) { + descriptors.add(descriptor); + } + pos += length; + } + return descriptors; } private static boolean checkValidPsipSection(byte[] data) { @@ -133,4 +187,29 @@ public class SampleTunerTvInputSectionParser { mMinorChannelNumber); } } + + /** + * A base class for TS descriptors + * For details of their structure, see ATSC A/65 Section 6.9 + */ + public abstract static class TsDescriptor { + public abstract int getTag(); + } + + public static class ExtendedChannelNameDescriptor extends TsDescriptor { + private final String mLongChannelName; + + public ExtendedChannelNameDescriptor(String longChannelName) { + mLongChannelName = longChannelName; + } + + @Override + public int getTag() { + return DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME; + } + + public String getLongChannelName() { + return mLongChannelName; + } + } } -- cgit v1.2.3 From 452b95870608726770cddb180fe5e69a09c7721a Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Thu, 30 Jun 2022 00:43:05 +0000 Subject: SampleTunerTIS parse ExtendedChannelNameDescriptor TVCT tables include a short name, which is a maximum of 7 characters long, and optionally an ExtendedChannelName inside of a Descriptor which can be of any length. This CL adds parsing for that ExtendedChannelName, without adding support for string compression or many different modes. Bug: 237323181 Test: Manually on cuttlefish. Channel name for the TIS should display as the ExtendedChannelName in input TS file. Change-Id: I1accf5a9fe4785b99bf84cef974538ac8341a5dd --- .../SampleTunerTvInputSectionParser.java | 58 +++++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java index 821597d1..14b6e65d 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java @@ -30,6 +30,8 @@ public class SampleTunerTvInputSectionParser { private static final boolean DEBUG = true; public static final byte DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME = (byte) 0xa0; + public static final byte COMPRESSION_TYPE_NO_COMPRESSION = (byte) 0x00; + public static final byte MODE_UTF16 = (byte) 0x3f; /** * Parses a single TVCT section, as defined in A/65 6.4 @@ -77,6 +79,9 @@ public class SampleTunerTvInputSectionParser { ExtendedChannelNameDescriptor longNameDescriptor = (ExtendedChannelNameDescriptor)descriptor; name = longNameDescriptor.getLongChannelName(); + if (DEBUG) { + Log.d(TAG, "parseTVCTSection found longName: " + name); + } } } @@ -110,8 +115,7 @@ public class SampleTunerTvInputSectionParser { TsDescriptor descriptor = null; switch ((byte) tag) { case DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME: - // TODO: Parse ExtendedChannelNameDescriptor from data - descriptor = new ExtendedChannelNameDescriptor("Sample"); + descriptor = parseExtendedChannelNameDescriptor(data, pos, pos + length); break; default: break; @@ -124,6 +128,56 @@ public class SampleTunerTvInputSectionParser { return descriptors; } + // ExtendedChannelNameDescriptor is defined in ATSC A/64 Section 6.9.4 + // Returns first string segment with supported compression and mode + // Returns null on invalid data or no supported string segments + private static ExtendedChannelNameDescriptor parseExtendedChannelNameDescriptor(byte[] data, + int offset, int limit) { + if (limit < offset + 8) { + Log.e(TAG, "parseExtendedChannelNameDescriptor given too little data"); + return null; + } + + // Here we read the Multiple String Structure which is embedded in the Descriptor + // This data structure is defined in ATSC A/65 Section 6.10 + int numStrings = data[offset] & 0xff; + if (numStrings <= 0) { + Log.e(TAG, "parseExtendedChannelNameDescriptor found no strings"); + return null; + } + int pos = offset + 1; + for (int i = 0; i < numStrings; i++) { + if (limit < pos + 4) { + Log.e(TAG, "parseExtendedChannelNameDescriptor ran out of data"); + return null; + } + int numSegments = data[pos + 3] & 0xff; + pos += 4; + for (int j = 0; j < numSegments; j++) { + if (limit < pos + 3) { + Log.e(TAG, "parseExtendedChannelNameDescriptor ran out of data"); + return null; + } + int compressionType = data[pos] & 0xff; + int mode = data[pos + 1] & 0xff; + int numBytes = data[pos + 2] & 0xff; + pos += 3; + if (data.length < pos + numBytes) { + Log.e(TAG, "parseExtendedChannelNameDescriptor ran out of data"); + return null; + } + if (compressionType == COMPRESSION_TYPE_NO_COMPRESSION && mode == MODE_UTF16) { + return new ExtendedChannelNameDescriptor(new String(data, pos, numBytes, + StandardCharsets.UTF_16)); + } + pos += numBytes; + } + } + + Log.e(TAG, "parseExtendedChannelNameDescriptor found no supported segments"); + return null; + } + private static boolean checkValidPsipSection(byte[] data) { if (data.length < 13) { Log.e(TAG, "Section was too small"); -- cgit v1.2.3 From 952c6de890bbe0b78b6afdba5783315415fcddad Mon Sep 17 00:00:00 2001 From: Sorin Basca Date: Thu, 7 Jul 2022 18:14:17 +0100 Subject: Use Mockito 4.6.1 API Bug: 236636175 Test: m TVUnitTests Test: m RunTvRoboTests Change-Id: I424015a4ec7e28c4775f7d2614d10d0958dcdb2e --- .../com/android/tv/dvr/recorder/SchedulerTest.java | 4 +- tests/unit/src/com/android/tv/menu/MenuTest.java | 85 ++++++++++++---------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java b/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java index 062947bc..887c45fe 100644 --- a/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java +++ b/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import android.app.AlarmManager; import android.app.PendingIntent; @@ -95,7 +95,7 @@ public class SchedulerTest { @Test public void testUpdate_none() { mScheduler.updateAndStartServiceIfNeeded(); - verifyZeroInteractions(mMockAlarmManager); + verifyNoInteractions(mMockAlarmManager); } @Test diff --git a/tests/unit/src/com/android/tv/menu/MenuTest.java b/tests/unit/src/com/android/tv/menu/MenuTest.java index 7058316e..a1cfdba0 100644 --- a/tests/unit/src/com/android/tv/menu/MenuTest.java +++ b/tests/unit/src/com/android/tv/menu/MenuTest.java @@ -18,15 +18,23 @@ package com.android.tv.menu; import static androidx.test.InstrumentationRegistry.getTargetContext; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; + import com.android.tv.menu.Menu.OnMenuVisibilityChangeListener; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; -import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -43,9 +51,8 @@ public class MenuTest { public void setUp() { mMenuView = Mockito.mock(IMenuView.class); MenuRowFactory factory = Mockito.mock(MenuRowFactory.class); - Mockito.when( - factory.createMenuRow( - ArgumentMatchers.any(Menu.class), ArgumentMatchers.any(Class.class))) + when(factory.createMenuRow( + any(Menu.class), any(Class.class))) .thenReturn(null); mVisibilityChangeListener = Mockito.mock(OnMenuVisibilityChangeListener.class); mMenu = new Menu(getTargetContext(), mMenuView, factory, mVisibilityChangeListener); @@ -81,22 +88,22 @@ public class MenuTest { mMenu.show(Menu.REASON_NONE); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_NONE), - Matchers.isNull(String.class), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_NONE), + isNull(), + isNull()); mMenu.hide(true); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } @Test @@ -105,22 +112,22 @@ public class MenuTest { mMenu.show(Menu.REASON_GUIDE); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_GUIDE), - Matchers.eq(ChannelsRow.ID), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_GUIDE), + eq(ChannelsRow.ID), + isNull()); mMenu.hide(false); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } @Test @@ -129,26 +136,26 @@ public class MenuTest { mMenu.show(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD), - Matchers.eq(PlayControlsRow.ID), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD), + eq(PlayControlsRow.ID), + isNull()); mMenu.hide(false); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } private void setMenuVisible(final boolean visible) { - Mockito.when(mMenuView.isVisible()) + when(mMenuView.isVisible()) .thenAnswer( new Answer() { @Override -- cgit v1.2.3 From 967d45dcb328d140fd9746ad8051c814df0f53e5 Mon Sep 17 00:00:00 2001 From: Nick Chalko Date: Tue, 12 Jul 2022 16:19:07 +0000 Subject: Remove nchalko from OWNERS file Change-Id: Ia6467d9f408ae95d17aaf166b3510ede14304518 --- OWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/OWNERS b/OWNERS index e904f5c6..a386cdaf 100644 --- a/OWNERS +++ b/OWNERS @@ -1,3 +1,2 @@ -nchalko@google.com shubang@google.com quxiangfang@google.com -- cgit v1.2.3 From 3600be6067d6d687b80206139ca03512b9a0f861 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Tue, 12 Jul 2022 23:41:56 +0000 Subject: SampleTunerTIS Use TransportStream for Media Input Currently, the TIS uses ES data for its Media input. This CL changes its configuration to instead use TS data. Since TS supports Section data being mixed together with Media data (unlike ES), this will allow future changes to read Section data in the Service. Bug: 238889790 Test: Manually with Cuttlefish using new ts input. Change-Id: I3693f37406dbd6d2975f422b6230cbe45d3d6f40 --- .../sampletunertvinput/SampleTunerTvInputService.java | 16 ++++++++-------- .../SampleTunerTvInputSetupActivity.java | 4 ++-- .../sampletunertvinput/SampleTunerTvInputUtils.java | 2 +- 3 files changed, 11 insertions(+), 11 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 a995e51a..1333c472 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -35,21 +35,21 @@ public class SampleTunerTvInputService extends TvInputService { private static final int TIMEOUT_US = 100000; private static final boolean SAVE_DATA = false; - private static final String ES_FILE_NAME = "test.es"; + private static final String MEDIA_INPUT_FILE_NAME = "media.ts"; private static final MediaFormat VIDEO_FORMAT; static { // format extracted for the specific input file - VIDEO_FORMAT = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 320, 240); + VIDEO_FORMAT = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 360); VIDEO_FORMAT.setInteger(MediaFormat.KEY_TRACK_ID, 1); - VIDEO_FORMAT.setLong(MediaFormat.KEY_DURATION, 9933333); - VIDEO_FORMAT.setInteger(MediaFormat.KEY_LEVEL, 32); + VIDEO_FORMAT.setLong(MediaFormat.KEY_DURATION, 10000000); + VIDEO_FORMAT.setInteger(MediaFormat.KEY_LEVEL, 256); 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}); + new byte[] {0, 0, 0, 1, 103, 66, -64, 30, -39, 1, -32, -65, -27, -64, 68, 0, 0, 3, + 0, 4, 0, 0, 3, 0, -16, 60, 88, -71, 32}); VIDEO_FORMAT.setByteBuffer("csd-0", csd); - csd = ByteBuffer.wrap(new byte[] {0, 0, 0, 1, 104, -50, 60, -128}); + csd = ByteBuffer.wrap(new byte[] {0, 0, 0, 1, 104, -53, -125, -53, 32}); VIDEO_FORMAT.setByteBuffer("csd-1", csd); } @@ -244,7 +244,7 @@ public class SampleTunerTvInputService extends TvInputService { mSectionFilter.start(); // use dvr playback to feed the data on platform without physical tuner mDvr = SampleTunerTvInputUtils.createDvrPlayback(mTuner, mHandler, - mContext, ES_FILE_NAME, DvrSettings.DATA_FORMAT_ES); + mContext, MEDIA_INPUT_FILE_NAME, DvrSettings.DATA_FORMAT_TS); SampleTunerTvInputUtils.tune(mTuner, mHandler, mDvr); mDvr.start(); mMediaCodec.start(); diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java index 7933ccc6..7147455e 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java @@ -28,7 +28,7 @@ public class SampleTunerTvInputSetupActivity extends Activity { private static final String TAG = "SampleTunerTvInput"; private static final boolean DEBUG = true; - private static final String ES_FILE_NAME = "test.ts"; + private static final String SETUP_INPUT_FILE_NAME = "setup.ts"; private Tuner mTuner; private DvrPlayback mDvr; @@ -151,7 +151,7 @@ public class SampleTunerTvInputSetupActivity extends Activity { mSectionFilter.start(); mDvr = SampleTunerTvInputUtils.createDvrPlayback(mTuner, handler, - getApplicationContext(), ES_FILE_NAME, DvrSettings.DATA_FORMAT_TS); + getApplicationContext(), SETUP_INPUT_FILE_NAME, DvrSettings.DATA_FORMAT_TS); SampleTunerTvInputUtils.tune(mTuner, handler, mDvr); mDvr.start(); } diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java index bcbfd7c3..3c44fd19 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputUtils.java @@ -50,7 +50,7 @@ public class SampleTunerTvInputUtils { private static final int DVR_BUFFER_SIZE = 4000000; private static final int PACKET_SIZE = 188; private static final long FREQUENCY = 578000; - private static final int INPUT_FILE_MAX_SIZE = 700000; + private static final int INPUT_FILE_MAX_SIZE = 1000000; public static DvrPlayback createDvrPlayback(Tuner tuner, Handler handler, Context context, String fileName, int dataFormat) { -- cgit v1.2.3 From 3abe18c176e760fe6856d87b387a0df10419c5f1 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Tue, 12 Jul 2022 23:15:37 +0000 Subject: SampleTunerTIS Add sleeping between media frames Currently, the TIS queues frames into the MediaCodec as quickly as it can, ignoring their Presentation Time Stamps. This CL instead has it properly wait in between each frame until the correct time, producing a smooth output of the correct length. Bug: 238807604 Test: Manually using Cuttlefish and video input. Compare the elapsed SystemTime vs. the known length of input. Change-Id: I2cef080a46b5e5d547c2cbe1e3d69811ee13282a --- .../SampleTunerTvInputService.java | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) 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 1333c472..e1925ccb 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -90,6 +90,8 @@ public class SampleTunerTvInputService extends TvInputService { private Thread mDecoderThread; private Deque mDataQueue; private List mSavedData; + private long mCurrentLoopStartTimeUs = 0; + private long mLastFramePtsUs = 0; private boolean mDataReady = false; @@ -342,6 +344,36 @@ public class SampleTunerTvInputService extends TvInputService { BufferInfo bufferInfo = new BufferInfo(); int res = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US); if (res >= 0) { + long currentFramePtsUs = bufferInfo.presentationTimeUs; + + // We know we are starting a new loop if the loop time is not set or if + // the current frame is before the last frame + if (mCurrentLoopStartTimeUs == 0 || currentFramePtsUs < mLastFramePtsUs) { + mCurrentLoopStartTimeUs = System.nanoTime() / 1000; + } + mLastFramePtsUs = currentFramePtsUs; + + long desiredUs = mCurrentLoopStartTimeUs + currentFramePtsUs; + long nowUs = System.nanoTime() / 1000; + long sleepTimeUs = desiredUs - nowUs; + + if (DEBUG) { + Log.d(TAG, "currentFramePts: " + currentFramePtsUs + + " sleeping for: " + sleepTimeUs); + } + if (sleepTimeUs > 0) { + try { + Thread.sleep( + /* millis */ sleepTimeUs / 1000, + /* nanos */ (int) (sleepTimeUs % 1000) * 1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + if (DEBUG) { + Log.d(TAG, "InterruptedException:\n" + Log.getStackTraceString(e)); + } + return; + } + } mMediaCodec.releaseOutputBuffer(res, true); notifyVideoAvailable(); if (DEBUG) { -- cgit v1.2.3 From 85e3a8d895bd454a3ebcb48e3bfeb8c754baadfd Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 13 Jul 2022 21:55:13 +0000 Subject: SampleTunerTIS Fix bug with mDataQueue overfilling Currently, when SAVE_DATA is enabled, all saved data is added to the mDataQueue on every loop of our thread. This results in the mDataQueue's size rapidly growing as the TIS runs. This CL changes the behavior to instead fill the mDataQueue only if it is currently empty, and adds logging for when it occurs. Bug: 238929236 Test: Manually with Cuttlefish. Change-Id: Ie928e58352ecc093ca8427e3f9c1b56263000e64 --- .../tv/samples/sampletunertvinput/SampleTunerTvInputService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 1333c472..ee02f75e 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -261,7 +261,10 @@ public class SampleTunerTvInputService extends TvInputService { mDataQueue.pollFirst(); } } - if (SAVE_DATA) { + else if (SAVE_DATA) { + if (DEBUG) { + Log.d(TAG, "Adding saved data to data queue"); + } mDataQueue.addAll(mSavedData); } } -- cgit v1.2.3 From f8a522d2c9db006cb6d7bb514efc4e22ff18a5a7 Mon Sep 17 00:00:00 2001 From: Sorin Basca Date: Thu, 7 Jul 2022 18:14:17 +0100 Subject: Use Mockito 4.6.1 API Bug: 236636175 Test: m TVUnitTests Test: m RunTvRoboTests Change-Id: I424015a4ec7e28c4775f7d2614d10d0958dcdb2e (cherry picked from commit 952c6de890bbe0b78b6afdba5783315415fcddad) Merged-In: I424015a4ec7e28c4775f7d2614d10d0958dcdb2e --- .../com/android/tv/dvr/recorder/SchedulerTest.java | 4 +- tests/unit/src/com/android/tv/menu/MenuTest.java | 85 ++++++++++++---------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java b/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java index 062947bc..887c45fe 100644 --- a/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java +++ b/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import android.app.AlarmManager; import android.app.PendingIntent; @@ -95,7 +95,7 @@ public class SchedulerTest { @Test public void testUpdate_none() { mScheduler.updateAndStartServiceIfNeeded(); - verifyZeroInteractions(mMockAlarmManager); + verifyNoInteractions(mMockAlarmManager); } @Test diff --git a/tests/unit/src/com/android/tv/menu/MenuTest.java b/tests/unit/src/com/android/tv/menu/MenuTest.java index 7058316e..a1cfdba0 100644 --- a/tests/unit/src/com/android/tv/menu/MenuTest.java +++ b/tests/unit/src/com/android/tv/menu/MenuTest.java @@ -18,15 +18,23 @@ package com.android.tv.menu; import static androidx.test.InstrumentationRegistry.getTargetContext; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; + import com.android.tv.menu.Menu.OnMenuVisibilityChangeListener; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; -import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -43,9 +51,8 @@ public class MenuTest { public void setUp() { mMenuView = Mockito.mock(IMenuView.class); MenuRowFactory factory = Mockito.mock(MenuRowFactory.class); - Mockito.when( - factory.createMenuRow( - ArgumentMatchers.any(Menu.class), ArgumentMatchers.any(Class.class))) + when(factory.createMenuRow( + any(Menu.class), any(Class.class))) .thenReturn(null); mVisibilityChangeListener = Mockito.mock(OnMenuVisibilityChangeListener.class); mMenu = new Menu(getTargetContext(), mMenuView, factory, mVisibilityChangeListener); @@ -81,22 +88,22 @@ public class MenuTest { mMenu.show(Menu.REASON_NONE); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_NONE), - Matchers.isNull(String.class), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_NONE), + isNull(), + isNull()); mMenu.hide(true); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } @Test @@ -105,22 +112,22 @@ public class MenuTest { mMenu.show(Menu.REASON_GUIDE); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_GUIDE), - Matchers.eq(ChannelsRow.ID), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_GUIDE), + eq(ChannelsRow.ID), + isNull()); mMenu.hide(false); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } @Test @@ -129,26 +136,26 @@ public class MenuTest { mMenu.show(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD), - Matchers.eq(PlayControlsRow.ID), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD), + eq(PlayControlsRow.ID), + isNull()); mMenu.hide(false); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } private void setMenuVisible(final boolean visible) { - Mockito.when(mMenuView.isVisible()) + when(mMenuView.isVisible()) .thenAnswer( new Answer() { @Override -- cgit v1.2.3 From 488c58f91b7481526d6d88c03564eae555310981 Mon Sep 17 00:00:00 2001 From: Sorin Basca Date: Thu, 7 Jul 2022 18:14:17 +0100 Subject: Use Mockito 4.6.1 API Bug: 236636175 Test: m TVUnitTests Test: m RunTvRoboTests Change-Id: I424015a4ec7e28c4775f7d2614d10d0958dcdb2e (cherry picked from commit f8a522d2c9db006cb6d7bb514efc4e22ff18a5a7) Merged-In: I424015a4ec7e28c4775f7d2614d10d0958dcdb2e --- .../com/android/tv/dvr/recorder/SchedulerTest.java | 4 +- tests/unit/src/com/android/tv/menu/MenuTest.java | 85 ++++++++++++---------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java b/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java index 062947bc..887c45fe 100644 --- a/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java +++ b/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import android.app.AlarmManager; import android.app.PendingIntent; @@ -95,7 +95,7 @@ public class SchedulerTest { @Test public void testUpdate_none() { mScheduler.updateAndStartServiceIfNeeded(); - verifyZeroInteractions(mMockAlarmManager); + verifyNoInteractions(mMockAlarmManager); } @Test diff --git a/tests/unit/src/com/android/tv/menu/MenuTest.java b/tests/unit/src/com/android/tv/menu/MenuTest.java index 7058316e..a1cfdba0 100644 --- a/tests/unit/src/com/android/tv/menu/MenuTest.java +++ b/tests/unit/src/com/android/tv/menu/MenuTest.java @@ -18,15 +18,23 @@ package com.android.tv.menu; import static androidx.test.InstrumentationRegistry.getTargetContext; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; + import com.android.tv.menu.Menu.OnMenuVisibilityChangeListener; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; -import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -43,9 +51,8 @@ public class MenuTest { public void setUp() { mMenuView = Mockito.mock(IMenuView.class); MenuRowFactory factory = Mockito.mock(MenuRowFactory.class); - Mockito.when( - factory.createMenuRow( - ArgumentMatchers.any(Menu.class), ArgumentMatchers.any(Class.class))) + when(factory.createMenuRow( + any(Menu.class), any(Class.class))) .thenReturn(null); mVisibilityChangeListener = Mockito.mock(OnMenuVisibilityChangeListener.class); mMenu = new Menu(getTargetContext(), mMenuView, factory, mVisibilityChangeListener); @@ -81,22 +88,22 @@ public class MenuTest { mMenu.show(Menu.REASON_NONE); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_NONE), - Matchers.isNull(String.class), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_NONE), + isNull(), + isNull()); mMenu.hide(true); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } @Test @@ -105,22 +112,22 @@ public class MenuTest { mMenu.show(Menu.REASON_GUIDE); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_GUIDE), - Matchers.eq(ChannelsRow.ID), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_GUIDE), + eq(ChannelsRow.ID), + isNull()); mMenu.hide(false); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } @Test @@ -129,26 +136,26 @@ public class MenuTest { mMenu.show(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD), - Matchers.eq(PlayControlsRow.ID), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD), + eq(PlayControlsRow.ID), + isNull()); mMenu.hide(false); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } private void setMenuVisible(final boolean visible) { - Mockito.when(mMenuView.isVisible()) + when(mMenuView.isVisible()) .thenAnswer( new Answer() { @Override -- cgit v1.2.3 From 293b9724c4d3be7825d89a8df2c705f2091544f6 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Fri, 15 Jul 2022 18:45:43 +0000 Subject: SampleTunerTIS fix program initialization in setup During the SetupActivity of the Sample TIS, the Channel's information is set with a mix of data acquired from the VCT and hardcoded values. One of these hardcoded values was an initial program. However, the ChannelUtils.updateChannels method called actually entirely ignored this program. Therefore it is replaced with a call to ProgramUtils which autogenerates programs to fill an entire day of scheduling. Bug: 239457222 Test: Run manually with Cuttlefish. Should see test programs filling up 24 hours of scheduling. Running with TIS EIT changes will see overlapping programs deleted when EIT is read. Change-Id: Ieb67545fc92c1eda27235783aea1bd54192d7186 --- .../SampleTunerTvInputSetupActivity.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java index 7147455e..df891f9c 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSetupActivity.java @@ -16,12 +16,15 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; +import com.android.tv.common.util.Clock; import com.android.tv.testing.data.ChannelInfo; import com.android.tv.testing.data.ChannelUtils; import com.android.tv.testing.data.ProgramInfo; +import com.android.tv.testing.data.ProgramUtils; import java.util.Collections; import java.util.Locale; +import java.util.concurrent.TimeUnit; /** Setup activity for SampleTunerTvInput */ public class SampleTunerTvInputSetupActivity extends Activity { @@ -83,23 +86,13 @@ public class SampleTunerTvInputSetupActivity extends Activity { .setAudioChannel(2) .setAudioLanguageCount(1) .setHasClosedCaption(false) - .setProgram( - new ProgramInfo( - "Sample Program", - "", - 0, - 0, - ProgramInfo.GEN_POSTER, - "Sample description", - ProgramInfo.GEN_DURATION, - null, - ProgramInfo.GEN_GENRE, - null)) .build(); Intent intent = getIntent(); String inputId = intent.getStringExtra(TvInputInfo.EXTRA_INPUT_ID); ChannelUtils.updateChannels(this, inputId, Collections.singletonList(channel)); + ProgramUtils.updateProgramForAllChannelsOf(this, inputId, Clock.SYSTEM, + TimeUnit.DAYS.toMillis(1)); setResult(Activity.RESULT_OK); finish(); -- cgit v1.2.3 From 231ccd51ad7519cf10bbf1729c8c68b1d5ddebe0 Mon Sep 17 00:00:00 2001 From: Sorin Basca Date: Thu, 7 Jul 2022 18:14:17 +0100 Subject: Use Mockito 4.6.1 API Bug: 236636175 Test: m TVUnitTests Test: m RunTvRoboTests Change-Id: I424015a4ec7e28c4775f7d2614d10d0958dcdb2e (cherry picked from commit 952c6de890bbe0b78b6afdba5783315415fcddad) Merged-In: I424015a4ec7e28c4775f7d2614d10d0958dcdb2e (cherry picked from commit f8a522d2c9db006cb6d7bb514efc4e22ff18a5a7) Merged-In: I424015a4ec7e28c4775f7d2614d10d0958dcdb2e --- .../com/android/tv/dvr/recorder/SchedulerTest.java | 4 +- tests/unit/src/com/android/tv/menu/MenuTest.java | 85 ++++++++++++---------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java b/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java index 062947bc..887c45fe 100644 --- a/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java +++ b/tests/robotests/src/com/android/tv/dvr/recorder/SchedulerTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import android.app.AlarmManager; import android.app.PendingIntent; @@ -95,7 +95,7 @@ public class SchedulerTest { @Test public void testUpdate_none() { mScheduler.updateAndStartServiceIfNeeded(); - verifyZeroInteractions(mMockAlarmManager); + verifyNoInteractions(mMockAlarmManager); } @Test diff --git a/tests/unit/src/com/android/tv/menu/MenuTest.java b/tests/unit/src/com/android/tv/menu/MenuTest.java index 7058316e..a1cfdba0 100644 --- a/tests/unit/src/com/android/tv/menu/MenuTest.java +++ b/tests/unit/src/com/android/tv/menu/MenuTest.java @@ -18,15 +18,23 @@ package com.android.tv.menu; import static androidx.test.InstrumentationRegistry.getTargetContext; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; + import com.android.tv.menu.Menu.OnMenuVisibilityChangeListener; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; -import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -43,9 +51,8 @@ public class MenuTest { public void setUp() { mMenuView = Mockito.mock(IMenuView.class); MenuRowFactory factory = Mockito.mock(MenuRowFactory.class); - Mockito.when( - factory.createMenuRow( - ArgumentMatchers.any(Menu.class), ArgumentMatchers.any(Class.class))) + when(factory.createMenuRow( + any(Menu.class), any(Class.class))) .thenReturn(null); mVisibilityChangeListener = Mockito.mock(OnMenuVisibilityChangeListener.class); mMenu = new Menu(getTargetContext(), mMenuView, factory, mVisibilityChangeListener); @@ -81,22 +88,22 @@ public class MenuTest { mMenu.show(Menu.REASON_NONE); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_NONE), - Matchers.isNull(String.class), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_NONE), + isNull(), + isNull()); mMenu.hide(true); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } @Test @@ -105,22 +112,22 @@ public class MenuTest { mMenu.show(Menu.REASON_GUIDE); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_GUIDE), - Matchers.eq(ChannelsRow.ID), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_GUIDE), + eq(ChannelsRow.ID), + isNull()); mMenu.hide(false); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } @Test @@ -129,26 +136,26 @@ public class MenuTest { mMenu.show(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD); setMenuVisible(true); // Listener should be called with "true" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(true)); - Mockito.verify(mVisibilityChangeListener, Mockito.never()) - .onMenuVisibilityChange(Matchers.eq(false)); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(true)); + verify(mVisibilityChangeListener, never()) + .onMenuVisibilityChange(eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView) + verify(mMenuView) .onShow( - Matchers.eq(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD), - Matchers.eq(PlayControlsRow.ID), - Matchers.isNull(Runnable.class)); + eq(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD), + eq(PlayControlsRow.ID), + isNull()); mMenu.hide(false); setMenuVisible(false); // Listener should be called with "false" argument. - Mockito.verify(mVisibilityChangeListener, Mockito.atLeastOnce()) - .onMenuVisibilityChange(Matchers.eq(false)); - Mockito.verify(mMenuView).onHide(); + verify(mVisibilityChangeListener, atLeastOnce()) + .onMenuVisibilityChange(eq(false)); + verify(mMenuView).onHide(); } private void setMenuVisible(final boolean visible) { - Mockito.when(mMenuView.isVisible()) + when(mMenuView.isVisible()) .thenAnswer( new Answer() { @Override -- cgit v1.2.3 From 74129d911940fd4f70a14e0a60bad2cee91f14ff Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 13 Jul 2022 22:27:34 +0000 Subject: SampleTunerTIS Create SectionFilterCallback in TIS Currently, the TIS uses a default logging callback to handle incoming SectionEvents. This CL replaces the default callback with a custom one that calls a new function to handle the events. Implementation of the handleSection function is left to future CLs. Bug: 238890289 Test: Manually with Cuttlefish and input including sections. AV input handling is not changed while section size is logged. Change-Id: I8669e94b88c3f34f0181b46974b6b92909d1f950 --- .../SampleTunerTvInputService.java | 44 +++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) 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 385d9e63..feb3c8e1 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -15,6 +15,7 @@ import android.media.tv.tuner.filter.FilterEvent; import android.media.tv.tuner.filter.MediaEvent; import android.media.tv.tuner.Tuner; import android.media.tv.TvInputService; +import android.media.tv.tuner.filter.SectionEvent; import android.net.Uri; import android.os.Handler; import android.util.Log; @@ -209,6 +210,42 @@ public class SampleTunerTvInputService extends TvInputService { }; } + private FilterCallback sectionFilterCallback() { + return new FilterCallback() { + @Override + public void onFilterEvent(Filter filter, FilterEvent[] events) { + if (DEBUG) { + Log.d(TAG, "onFilterEvent section, size=" + events.length); + } + for (int i = 0; i < events.length; i++) { + if (DEBUG) { + Log.d(TAG, "events[" + i + "] is " + + events[i].getClass().getSimpleName()); + } + if (events[i] instanceof SectionEvent) { + SectionEvent sectionEvent = (SectionEvent) events[i]; + int dataSize = (int)sectionEvent.getDataLengthLong(); + if (DEBUG) { + Log.d(TAG, "section dataSize:" + dataSize); + } + + byte[] data = new byte[dataSize]; + filter.read(data, 0, dataSize); + + handleSection(data); + } + } + } + + @Override + public void onFilterStatusChanged(Filter filter, int status) { + if (DEBUG) { + Log.d(TAG, "onFilterStatusChanged section, status=" + status); + } + } + }; + } + private boolean initCodec() { if (mMediaCodec != null) { mMediaCodec.release(); @@ -240,7 +277,7 @@ public class SampleTunerTvInputService extends TvInputService { mVideoFilter = SampleTunerTvInputUtils.createAvFilter(mTuner, mHandler, videoFilterCallback(), false); mSectionFilter = SampleTunerTvInputUtils.createSectionFilter(mTuner, mHandler, - SampleTunerTvInputUtils.createDefaultLoggingFilterCallback("section")); + sectionFilterCallback()); mAudioFilter.start(); mVideoFilter.start(); mSectionFilter.start(); @@ -275,6 +312,11 @@ public class SampleTunerTvInputService extends TvInputService { } } + + private void handleSection(byte[] data) { + // TODO: Parse received section + } + private boolean handleDataBuffer(MediaEvent mediaEvent) { if (mediaEvent.getLinearBlock() == null) { if (DEBUG) Log.d(TAG, "getLinearBlock() == null"); -- cgit v1.2.3 From bcc485be2c12fa07e4491665621250cd426371e6 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 13 Jul 2022 22:43:28 +0000 Subject: SampleTunerTIS Create EitEventInfo This CL creates the data structure and method which will be used by the SectionParser to handle EIT sections. EitEventInfo currently only holds the data that will be immediately parsed within the next few CLs, with possible further expansions depending on future implementation decisions. Bug: 238890289 Test: Manually with Cuttlefish Change-Id: I4d20fd49994b33472839fb0e4ccfa27a8f4a8d4a --- .../SampleTunerTvInputSectionParser.java | 47 ++++++++++++++++++++++ .../SampleTunerTvInputService.java | 4 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java index 14b6e65d..39dfb908 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java @@ -88,6 +88,20 @@ public class SampleTunerTvInputSectionParser { return new TvctChannelInfo(name, majorNumber, minorNumber); } + /** + * Parses a single EIT section, as defined in ATSC A/65 Section 6.5 + * @param data, a byte array containing a single EIT section which describes only one event + * @return {@code null} if there is an error while parsing, the event with parsed data otherwise + */ + public static EitEventInfo parseEitSection(byte[] data) { + if (!checkValidPsipSection(data)) { + return null; + } + // TODO: Parse data and replace sample values + return new EitEventInfo("Sample Title", 3600); + } + + // Descriptor data structure defined in ISO/IEC 13818-1 Section 2.6 // Returns an empty list on parsing failures private static List parseDescriptors(byte[] data, int offset, int limit) { @@ -242,6 +256,39 @@ public class SampleTunerTvInputSectionParser { } } + /** + * Contains the portion of the data contained in the EIT used by + * our SampleTunerTvInputService + */ + public static class EitEventInfo { + private final String mEventTitle; + private final int mLengthSeconds; + + public EitEventInfo( + String eventTitle, + int lengthSeconds) { + mEventTitle = eventTitle; + mLengthSeconds = lengthSeconds; + } + + public String getEventTitle() { + return mEventTitle; + } + + public int getLengthSeconds() { + return mLengthSeconds; + } + + @Override + public String toString() { + return String.format( + Locale.US, + "Event Title: %s Length in Seconds: %d", + mEventTitle, + mLengthSeconds); + } + } + /** * A base class for TS descriptors * For details of their structure, see ATSC A/65 Section 6.9 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 feb3c8e1..cf9ee070 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -314,7 +314,9 @@ public class SampleTunerTvInputService extends TvInputService { private void handleSection(byte[] data) { - // TODO: Parse received section + SampleTunerTvInputSectionParser.EitEventInfo eventInfo = + SampleTunerTvInputSectionParser.parseEitSection(data); + // TODO: Update current program with our event information } private boolean handleDataBuffer(MediaEvent mediaEvent) { -- cgit v1.2.3 From 5e972e79bbb78d4f025aae515ab904cc65410f5d Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 13 Jul 2022 23:00:11 +0000 Subject: SampleTunerTIS Insert program from EitEventInfo This CL takes the EitEventInfo which [will be] parsed inside of parseEitSection() and inserts it into the program information of the running TV Application. Before insertion, it removes all programs which would conflict with its running time. Bug: 238890289 Test: Manually with Cuttlefish. Should see created program with sample title and length. Repeatedly opening and closing the channel will see the program replaced to match current starting time. Change-Id: Ibec1e259e7063a5edb62f921c63f3d5cc0315c02 --- .../SampleTunerTvInputService.java | 37 ++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 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 cf9ee070..12b10405 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -2,11 +2,14 @@ package com.android.tv.samples.sampletunertvinput; import static android.media.tv.TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN; +import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.LinearBlock; import android.media.MediaFormat; +import android.media.tv.TvContract; import android.media.tv.tuner.dvr.DvrPlayback; import android.media.tv.tuner.dvr.DvrSettings; import android.media.tv.tuner.filter.Filter; @@ -21,6 +24,8 @@ import android.os.Handler; import android.util.Log; import android.view.Surface; +import com.android.tv.common.util.Clock; + import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayDeque; @@ -57,6 +62,7 @@ public class SampleTunerTvInputService extends TvInputService { public static final String INPUT_ID = "com.android.tv.samples.sampletunertvinput/.SampleTunerTvInputService"; private String mSessionId; + private Uri mChannelUri; @Override public TvInputSessionImpl onCreateSession(String inputId, String sessionId) { @@ -160,6 +166,7 @@ public class SampleTunerTvInputService extends TvInputService { Log.e(TAG, "null codec!"); return false; } + mChannelUri = uri; mHandler = new Handler(); mDecoderThread = new Thread( @@ -312,11 +319,37 @@ public class SampleTunerTvInputService extends TvInputService { } } - private void handleSection(byte[] data) { SampleTunerTvInputSectionParser.EitEventInfo eventInfo = SampleTunerTvInputSectionParser.parseEitSection(data); - // TODO: Update current program with our event information + if (eventInfo == null) { + Log.e(TAG, "Did not receive event info from parser"); + return; + } + + // We assume that our program starts at the current time + long startTimeMs = Clock.SYSTEM.currentTimeMillis(); + long endTimeMs = startTimeMs + ((long)eventInfo.getLengthSeconds() * 1000); + + // Remove any other programs which conflict with our start and end time + Uri conflictsUri = + TvContract.buildProgramsUriForChannel(mChannelUri, startTimeMs, endTimeMs); + int programsDeleted = mContext.getContentResolver().delete(conflictsUri, null, null); + if (DEBUG) { + Log.d(TAG, "Deleted " + programsDeleted + " conflicting program(s)"); + } + + // Insert our new program into the newly opened time slot + ContentValues values = new ContentValues(); + values.put(TvContract.Programs.COLUMN_CHANNEL_ID, ContentUris.parseId(mChannelUri)); + values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, startTimeMs); + values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, endTimeMs); + values.put(TvContract.Programs.COLUMN_TITLE, eventInfo.getEventTitle()); + values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, ""); + if (DEBUG) { + Log.d(TAG, "Inserting program with values: " + values); + } + mContext.getContentResolver().insert(TvContract.Programs.CONTENT_URI, values); } private boolean handleDataBuffer(MediaEvent mediaEvent) { -- cgit v1.2.3 From 18a69c1dc16e0adbbcd1106935007c49fdb5f2a3 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Thu, 14 Jul 2022 23:27:10 +0000 Subject: SampleTunerTIS Seperate parseMultiString Previously, we implemented a parseExtendedChannelNameDescriptor method. However, this descriptor is defined as simply a tag and length, followed by a MultipleStringStructure. This CL moves the parsing of this string structure into its own seperate method so that it can be used in the parsing of EIT sections. Bug: 238890289 Test: Manually with Cuttlefish. Using bad string structures in existing ExtendedChannelNameDescriptors should result in new error logs, with no other functionality changes. Change-Id: I8367b8467381efce8f430de4abf3cdbe4dfea933 --- .../SampleTunerTvInputSectionParser.java | 31 +++++++++++++--------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java index 39dfb908..7b5c16f6 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java @@ -142,34 +142,40 @@ public class SampleTunerTvInputSectionParser { return descriptors; } - // ExtendedChannelNameDescriptor is defined in ATSC A/64 Section 6.9.4 - // Returns first string segment with supported compression and mode - // Returns null on invalid data or no supported string segments + // ExtendedChannelNameDescriptor is defined in ATSC A/65 Section 6.9.4 as containing only + // a single MultipleStringStructure after its tag and length. + // @return {@code null} if parsing MultipleStringStructure fails private static ExtendedChannelNameDescriptor parseExtendedChannelNameDescriptor(byte[] data, int offset, int limit) { + String channelName = parseMultipleStringStructure(data, offset, limit); + return channelName == null ? null : new ExtendedChannelNameDescriptor(channelName); + } + + // MultipleStringStructure is defined in ATSC A/65 Section 6.10 + // Returns first string segment with supported compression and mode + // @return {@code null} on invalid data or no supported string segments + private static String parseMultipleStringStructure(byte[] data, int offset, int limit) { if (limit < offset + 8) { - Log.e(TAG, "parseExtendedChannelNameDescriptor given too little data"); + Log.e(TAG, "parseMultipleStringStructure given too little data"); return null; } - // Here we read the Multiple String Structure which is embedded in the Descriptor - // This data structure is defined in ATSC A/65 Section 6.10 int numStrings = data[offset] & 0xff; if (numStrings <= 0) { - Log.e(TAG, "parseExtendedChannelNameDescriptor found no strings"); + Log.e(TAG, "parseMultipleStringStructure found no strings"); return null; } int pos = offset + 1; for (int i = 0; i < numStrings; i++) { if (limit < pos + 4) { - Log.e(TAG, "parseExtendedChannelNameDescriptor ran out of data"); + Log.e(TAG, "parseMultipleStringStructure ran out of data"); return null; } int numSegments = data[pos + 3] & 0xff; pos += 4; for (int j = 0; j < numSegments; j++) { if (limit < pos + 3) { - Log.e(TAG, "parseExtendedChannelNameDescriptor ran out of data"); + Log.e(TAG, "parseMultipleStringStructure ran out of data"); return null; } int compressionType = data[pos] & 0xff; @@ -177,18 +183,17 @@ public class SampleTunerTvInputSectionParser { int numBytes = data[pos + 2] & 0xff; pos += 3; if (data.length < pos + numBytes) { - Log.e(TAG, "parseExtendedChannelNameDescriptor ran out of data"); + Log.e(TAG, "parseMultipleStringStructure ran out of data"); return null; } if (compressionType == COMPRESSION_TYPE_NO_COMPRESSION && mode == MODE_UTF16) { - return new ExtendedChannelNameDescriptor(new String(data, pos, numBytes, - StandardCharsets.UTF_16)); + return new String(data, pos, numBytes, StandardCharsets.UTF_16); } pos += numBytes; } } - Log.e(TAG, "parseExtendedChannelNameDescriptor found no supported segments"); + Log.e(TAG, "parseMultipleStringStructure found no supported segments"); return null; } -- cgit v1.2.3 From 0907172898b6591f623d9be6fe8558d8e1bab19b Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Thu, 14 Jul 2022 23:19:32 +0000 Subject: SampleTunerTIS Add parsing for EIT Events This CL replaces the existing sample values for EIT with real parsing for the EitEventInfo, including the event title and its length in seconds. Bug: 238890289 Test: Manually using Cuttlefish. Created events should have title and length from input instead of sample values. Change-Id: I5c33ee0d7919bcd7039e93b5ee4b18253f3235d6 --- .../SampleTunerTvInputSectionParser.java | 24 ++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java index 7b5c16f6..20c73de4 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputSectionParser.java @@ -97,8 +97,28 @@ public class SampleTunerTvInputSectionParser { if (!checkValidPsipSection(data)) { return null; } - // TODO: Parse data and replace sample values - return new EitEventInfo("Sample Title", 3600); + int numEvents = data[9] & 0xff; + if(numEvents != 1) { + Log.e(TAG, "parseEitSection expected 1 event, found " + numEvents); + return null; + } + // EIT Sections are a minimum of 14 bytes, with a minimum of 12 bytes per event + if(data.length < 26) { + Log.e(TAG, "parseEitSection found section under minimum length"); + return null; + } + + // Data field positions are as defined by A/65 Section 6.5 for one event + int lengthInSeconds = ((data[16] & 0x0f) << 16) | ((data[17] & 0xff) << 8) + | (data[18] & 0xff); + int titleLength = data[19] & 0xff; + String titleText = parseMultipleStringStructure(data, 20, 20 + titleLength); + + if (DEBUG) { + Log.d(TAG, "parseEitSection found titleText: " + titleText + + " lengthInSeconds: " + lengthInSeconds); + } + return new EitEventInfo(titleText, lengthInSeconds); } -- cgit v1.2.3 From a1eca03cfc8e11ce35a07ebd191513457c5d1d07 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Tue, 19 Jul 2022 21:44:34 +0000 Subject: SampleTIAS initial create This CL is the initial creation of an empty SampleTIAS. To run, requires modified LiveTv to handle AIT info (Currently located on partner branch at CL 2204579). With modified LiveTv, will begin once currently running TIS calls notifyUpdateAitInfo(). Bug: 239866882 Test: Manually using cuttlefish and steps outlined above. Change-Id: I5c76fa79526b2d23ca696e1171cafd05589a1c6f --- .../SampleTvInteractiveAppService/Android.bp | 49 ++++++++++++++++++++ .../AndroidManifest.xml | 50 +++++++++++++++++++++ .../SampleTvInteractiveAppService/build.gradle | 31 +++++++++++++ .../res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/values/strings.xml | 25 +++++++++++ .../res/xml/tviappservice.xml | 19 ++++++++ .../SampleTvInteractiveAppService.java | 37 +++++++++++++++ .../TiasSessionImpl.java | 46 +++++++++++++++++++ 12 files changed, 257 insertions(+) create mode 100644 interactive/SampleTvInteractiveAppService/Android.bp create mode 100644 interactive/SampleTvInteractiveAppService/AndroidManifest.xml create mode 100644 interactive/SampleTvInteractiveAppService/build.gradle create mode 100644 interactive/SampleTvInteractiveAppService/res/mipmap-hdpi/ic_launcher.webp create mode 100644 interactive/SampleTvInteractiveAppService/res/mipmap-mdpi/ic_launcher.webp create mode 100644 interactive/SampleTvInteractiveAppService/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 interactive/SampleTvInteractiveAppService/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 interactive/SampleTvInteractiveAppService/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 interactive/SampleTvInteractiveAppService/res/values/strings.xml create mode 100644 interactive/SampleTvInteractiveAppService/res/xml/tviappservice.xml create mode 100644 interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/SampleTvInteractiveAppService.java create mode 100644 interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java diff --git a/interactive/SampleTvInteractiveAppService/Android.bp b/interactive/SampleTvInteractiveAppService/Android.bp new file mode 100644 index 00000000..eada4ded --- /dev/null +++ b/interactive/SampleTvInteractiveAppService/Android.bp @@ -0,0 +1,49 @@ +// +// Copyright (C) 2022 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_app { + name: "SampleTvInteractiveAppService", + + srcs: ["src/**/*.java"], + optimize: { + enabled: false, + }, + + privileged: true, + product_specific: true, + sdk_version: "system_current", + min_sdk_version: "33", // T + + resource_dirs: [ + "res", + ], + + static_libs: [ + "androidx.leanback_leanback", + ], + + aaptflags: [ + "--version-name", + version_name, + + "--version-code", + version_code, + ], +} diff --git a/interactive/SampleTvInteractiveAppService/AndroidManifest.xml b/interactive/SampleTvInteractiveAppService/AndroidManifest.xml new file mode 100644 index 00000000..d631b956 --- /dev/null +++ b/interactive/SampleTvInteractiveAppService/AndroidManifest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/interactive/SampleTvInteractiveAppService/build.gradle b/interactive/SampleTvInteractiveAppService/build.gradle new file mode 100644 index 00000000..a51bc56a --- /dev/null +++ b/interactive/SampleTvInteractiveAppService/build.gradle @@ -0,0 +1,31 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 31 + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + applicationId "com.android.tv.samples.sampletvinteractiveappservice" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + } + android.applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = "SampleTvInteractiveAppService-v${defaultConfig.versionName}.apk" + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} +dependencies { + implementation 'androidx.leanback:leanback:1.0.0' +} \ No newline at end of file diff --git a/interactive/SampleTvInteractiveAppService/res/mipmap-hdpi/ic_launcher.webp b/interactive/SampleTvInteractiveAppService/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 00000000..c209e78e Binary files /dev/null and b/interactive/SampleTvInteractiveAppService/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/interactive/SampleTvInteractiveAppService/res/mipmap-mdpi/ic_launcher.webp b/interactive/SampleTvInteractiveAppService/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 00000000..4f0f1d64 Binary files /dev/null and b/interactive/SampleTvInteractiveAppService/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/interactive/SampleTvInteractiveAppService/res/mipmap-xhdpi/ic_launcher.webp b/interactive/SampleTvInteractiveAppService/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..948a3070 Binary files /dev/null and b/interactive/SampleTvInteractiveAppService/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/interactive/SampleTvInteractiveAppService/res/mipmap-xxhdpi/ic_launcher.webp b/interactive/SampleTvInteractiveAppService/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 00000000..28d4b77f Binary files /dev/null and b/interactive/SampleTvInteractiveAppService/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/interactive/SampleTvInteractiveAppService/res/mipmap-xxxhdpi/ic_launcher.webp b/interactive/SampleTvInteractiveAppService/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 00000000..aa7d6427 Binary files /dev/null and b/interactive/SampleTvInteractiveAppService/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/interactive/SampleTvInteractiveAppService/res/values/strings.xml b/interactive/SampleTvInteractiveAppService/res/values/strings.xml new file mode 100644 index 00000000..485b7290 --- /dev/null +++ b/interactive/SampleTvInteractiveAppService/res/values/strings.xml @@ -0,0 +1,25 @@ + + + + + SampleTvInteractiveAppService + + hbbtv + ginga + atsc + + \ No newline at end of file diff --git a/interactive/SampleTvInteractiveAppService/res/xml/tviappservice.xml b/interactive/SampleTvInteractiveAppService/res/xml/tviappservice.xml new file mode 100644 index 00000000..87020f26 --- /dev/null +++ b/interactive/SampleTvInteractiveAppService/res/xml/tviappservice.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/SampleTvInteractiveAppService.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/SampleTvInteractiveAppService.java new file mode 100644 index 00000000..828e2188 --- /dev/null +++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/SampleTvInteractiveAppService.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 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.samples.sampletvinteractiveappservice; + +import android.media.tv.interactive.TvInteractiveAppService; +import android.util.Log; + +public class SampleTvInteractiveAppService extends TvInteractiveAppService { + private static final String TAG = "SampleTvInteractiveAppService"; + private static final boolean DEBUG = true; + + @Override + public void onCreate() { + super.onCreate(); + } + + @Override + public Session onCreateSession(String iAppServiceId, int type) { + if (DEBUG) { + Log.d(TAG, "onCreateSession iAppServiceId=" + iAppServiceId + "type=" + type); + } + return new TiasSessionImpl(this); + } +} diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java new file mode 100644 index 00000000..c27ab435 --- /dev/null +++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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.samples.sampletvinteractiveappservice; + +import android.content.Context; +import android.media.tv.interactive.TvInteractiveAppService; +import android.util.Log; +import android.view.Surface; + +public class TiasSessionImpl extends TvInteractiveAppService.Session { + private static final String TAG = "SampleTvInteractiveAppService"; + private static final boolean DEBUG = true; + + public TiasSessionImpl(Context context) { + super(context); + } + + @Override + public void onRelease() { + if (DEBUG) { + Log.d(TAG, "onRelease"); + } + } + + @Override + public boolean onSetSurface(Surface surface) { + if (DEBUG) { + Log.d(TAG, "onSetSurface"); + } + return false; + } +} -- cgit v1.2.3 From b623313a337f748088cc050058ce63939a47014c Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Thu, 21 Jul 2022 21:17:01 +0000 Subject: SampleTIAS Notify manager once prepared This CL has the TIAS notify its manager that it is ready after being initialized. This prompts the manager to start the InteractiveApp. Bug: 239866882 Test: Manually using Cuttlefish. Should see logging for onStartInteractiveApp. Change-Id: I114e0775d94b7841944b60b01251b3ac8076b3c9 --- .../SampleTvInteractiveAppService.java | 4 ++- .../TiasSessionImpl.java | 40 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/SampleTvInteractiveAppService.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/SampleTvInteractiveAppService.java index 828e2188..c53748eb 100644 --- a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/SampleTvInteractiveAppService.java +++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/SampleTvInteractiveAppService.java @@ -32,6 +32,8 @@ public class SampleTvInteractiveAppService extends TvInteractiveAppService { if (DEBUG) { Log.d(TAG, "onCreateSession iAppServiceId=" + iAppServiceId + "type=" + type); } - return new TiasSessionImpl(this); + TiasSessionImpl session = new TiasSessionImpl(this, iAppServiceId, type); + session.prepare(this); + return session; } } diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java index c27ab435..32cdd71e 100644 --- a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java +++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java @@ -17,7 +17,9 @@ package com.android.tv.samples.sampletvinteractiveappservice; import android.content.Context; +import android.media.tv.interactive.TvInteractiveAppManager; import android.media.tv.interactive.TvInteractiveAppService; +import android.os.Handler; import android.util.Log; import android.view.Surface; @@ -25,8 +27,17 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { private static final String TAG = "SampleTvInteractiveAppService"; private static final boolean DEBUG = true; - public TiasSessionImpl(Context context) { + private final Handler mHandler; + private final int mType; + + public TiasSessionImpl(Context context, String iAppServiceId, int type) { super(context); + if (DEBUG) { + Log.d(TAG, "Constructing service with iAppServiceId=" + iAppServiceId + + " type=" + type); + } + mType = type; + mHandler = new Handler(context.getMainLooper()); } @Override @@ -43,4 +54,31 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { } return false; } + + @Override + public void onStartInteractiveApp() { + if (DEBUG) { + Log.d(TAG, "onStartInteractiveApp"); + } + } + + @Override + public void onStopInteractiveApp() { + if (DEBUG) { + Log.d(TAG, "onStopInteractiveApp"); + } + } + + public void prepare(TvInteractiveAppService serviceCaller) { + // Slightly delay our post to ensure the Manager has had time to register our Session + mHandler.postDelayed( + () -> { + if (serviceCaller != null) { + serviceCaller.notifyStateChanged(mType, + TvInteractiveAppManager.SERVICE_STATE_READY, + TvInteractiveAppManager.ERROR_NONE); + } + }, + 100); + } } -- cgit v1.2.3 From e2f03f645e8b69aad8ba75a1c70fb3070397ad40 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 20 Jul 2022 22:04:40 +0000 Subject: SampleTunerTIS Store MediaEventData to allow recycling MediaEvents The SampleTunerTIS receives MediaEvents from its filter which are originally created through the tuner JNI code. In our current code, the recycle() method is never called on the LinearBlocks generated by the native code, which causes a memory leak that leads to crashes. This CL fixes this problem by making a new MediaEventData java class which we use to store the data which is normally held in MediaEvents. This allows us to recycle MediaEvents as soon as we receieve them, allowing our JNI code to deallocate the memory they would otherwise take up. Bug: 239732425 Test: Manually with Cuttlefish. Memory usage no longer stays elevated after TIS is closed, and crashes are fixed. Change-Id: Ic4689078493611c150d5b4eb10b4ed7cc1123bea --- .../SampleTunerTvInputService.java | 128 ++++++++++++++------- 1 file changed, 87 insertions(+), 41 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 12b10405..34e0e573 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -7,7 +7,6 @@ import android.content.ContentValues; import android.content.Context; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; -import android.media.MediaCodec.LinearBlock; import android.media.MediaFormat; import android.media.tv.TvContract; import android.media.tv.tuner.dvr.DvrPlayback; @@ -95,8 +94,8 @@ public class SampleTunerTvInputService extends TvInputService { private Tuner mTuner; private MediaCodec mMediaCodec; private Thread mDecoderThread; - private Deque mDataQueue; - private List mSavedData; + private Deque mDataQueue; + private List mSavedData; private long mCurrentLoopStartTimeUs = 0; private long mLastFramePtsUs = 0; private boolean mDataReady = false; @@ -197,9 +196,14 @@ public class SampleTunerTvInputService extends TvInputService { } if (events[i] instanceof MediaEvent) { MediaEvent me = (MediaEvent) events[i]; - mDataQueue.add(me); + + MediaEventData storedEvent = MediaEventData.generateEventData(me); + if (storedEvent == null) { + continue; + } + mDataQueue.add(storedEvent); if (SAVE_DATA) { - mSavedData.add(me); + mSavedData.add(storedEvent); } } } @@ -352,24 +356,17 @@ public class SampleTunerTvInputService extends TvInputService { mContext.getContentResolver().insert(TvContract.Programs.CONTENT_URI, values); } - private boolean handleDataBuffer(MediaEvent mediaEvent) { - if (mediaEvent.getLinearBlock() == null) { - if (DEBUG) Log.d(TAG, "getLinearBlock() == null"); - return true; - } + private boolean handleDataBuffer(MediaEventData mediaEventData) { boolean success = false; - LinearBlock block = mediaEvent.getLinearBlock(); - if (queueCodecInputBuffer(block, mediaEvent.getDataLength(), mediaEvent.getOffset(), - mediaEvent.getPts())) { + if (queueCodecInputBuffer(mediaEventData.getData(), mediaEventData.getDataSize(), + mediaEventData.getPts())) { releaseCodecOutputBuffer(); success = true; } - mediaEvent.release(); return success; } - private boolean queueCodecInputBuffer(LinearBlock block, long sampleSize, - long offset, long pts) { + private boolean queueCodecInputBuffer(byte[] data, int size, long pts) { int res = mMediaCodec.dequeueInputBuffer(TIMEOUT_US); if (res >= 0) { ByteBuffer buffer = mMediaCodec.getInputBuffer(res); @@ -377,41 +374,19 @@ public class SampleTunerTvInputService extends TvInputService { throw new RuntimeException("Null decoder input buffer"); } - ByteBuffer data = block.map(); - if (offset > 0 && offset < data.limit()) { - data.position((int) 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())); + + size); } // fill codec input buffer - int size = sampleSize > data.limit() ? data.limit() : (int) sampleSize; - if (DEBUG) Log.d(TAG, "limit " + data.limit() + " sampleSize " + sampleSize); - if (data.hasArray()) { - Log.d(TAG, "hasArray"); - buffer.put(data.array(), 0, size); - } else { - byte[] array = new byte[size]; - data.get(array, 0, size); - buffer.put(array, 0, size); - } + buffer.put(data, 0, size); - mMediaCodec.queueInputBuffer(res, 0, (int) sampleSize, pts, 0); + mMediaCodec.queueInputBuffer(res, 0, size, pts, 0); } else { if (DEBUG) Log.d(TAG, "queueCodecInputBuffer res=" + res); return false; @@ -476,4 +451,75 @@ public class SampleTunerTvInputService extends TvInputService { } } + + /** + * MediaEventData is a helper class which is used to hold the data within MediaEvents + * locally in our Java code, instead of in the position allocated by our native code + */ + public static class MediaEventData { + private final long mPts; + private final int mDataSize; + private final byte[] mData; + + public MediaEventData(long pts, int dataSize, byte[] data) { + mPts = pts; + mDataSize = dataSize; + mData = data; + } + + /** + * Parses a MediaEvent, including copying its data and freeing the underlying LinearBlock + * @return {@code null} if the event has no LinearBlock + */ + public static MediaEventData generateEventData(MediaEvent event) { + if(event.getLinearBlock() == null) { + if (DEBUG) { + Log.d(TAG, "MediaEvent had null LinearBlock"); + } + return null; + } + + ByteBuffer memoryBlock = event.getLinearBlock().map(); + int eventOffset = (int)event.getOffset(); + int eventDataLength = (int)event.getDataLength(); + if (DEBUG) { + Log.d(TAG, "MediaEvent has length=" + eventDataLength + + " offset=" + eventOffset + + " capacity=" + memoryBlock.capacity() + + " limit=" + memoryBlock.limit()); + } + if (eventOffset < 0 || eventDataLength < 0 || eventOffset >= memoryBlock.limit()) { + if (DEBUG) { + Log.e(TAG, "MediaEvent length or offset was invalid"); + } + event.getLinearBlock().recycle(); + event.release(); + return null; + } + // We allow the case of eventOffset + eventDataLength > memoryBlock.limit() + // When it occurs, we read until memoryBlock.limit + int dataSize = Math.min(eventDataLength, memoryBlock.limit() - eventOffset); + memoryBlock.position(eventOffset); + + byte[] memoryData = new byte[dataSize]; + memoryBlock.get(memoryData, 0, dataSize); + MediaEventData eventData = new MediaEventData(event.getPts(), dataSize, memoryData); + + event.getLinearBlock().recycle(); + event.release(); + return eventData; + } + + public long getPts() { + return mPts; + } + + public int getDataSize() { + return mDataSize; + } + + public byte[] getData() { + return mData; + } + } } -- cgit v1.2.3 From 14ccd5b79f72f0af29cd3069661b12d7963b6981 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Thu, 21 Jul 2022 21:36:31 +0000 Subject: SampleTIAS Request info on start This CL has the TIAS request the current inputId, channelUri, and trackInfoList once it is started and logs their values once they are received. These values will be stored later once they are actually being used. Bug: 239866882 Test: Manually with Cuttlefish. Should see all 3 values received immediately after starting the TIAS. Change-Id: I0113d5fe5b14ef538188a62781d4f05d1dd70e44 --- .../TiasSessionImpl.java | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java index 32cdd71e..5c499838 100644 --- a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java +++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java @@ -17,12 +17,16 @@ package com.android.tv.samples.sampletvinteractiveappservice; import android.content.Context; +import android.media.tv.TvTrackInfo; import android.media.tv.interactive.TvInteractiveAppManager; import android.media.tv.interactive.TvInteractiveAppService; +import android.net.Uri; import android.os.Handler; import android.util.Log; import android.view.Surface; +import java.util.List; + public class TiasSessionImpl extends TvInteractiveAppService.Session { private static final String TAG = "SampleTvInteractiveAppService"; private static final boolean DEBUG = true; @@ -60,6 +64,13 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { if (DEBUG) { Log.d(TAG, "onStartInteractiveApp"); } + mHandler.post( + () -> { + requestCurrentTvInputId(); + requestCurrentChannelUri(); + requestTrackInfoList(); + } + ); } @Override @@ -81,4 +92,32 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { }, 100); } + + @Override + public void onCurrentChannelUri(Uri channelUri) { + if (DEBUG) { + Log.d(TAG, "onCurrentChannelUri uri=" + channelUri); + } + } + + @Override + public void onTrackInfoList(List tracks) { + if (DEBUG) { + Log.d(TAG, "onTrackInfoList size=" + tracks.size()); + for (int i = 0; i < tracks.size(); i++) { + TvTrackInfo trackInfo = tracks.get(i); + if (trackInfo != null) { + Log.d(TAG, "track " + i + ": type=" + trackInfo.getType() + + " id=" + trackInfo.getId()); + } + } + } + } + + @Override + public void onCurrentTvInputId(String inputId) { + if (DEBUG) { + Log.d(TAG, "onCurrentTvInputId id=" + inputId); + } + } } -- cgit v1.2.3 From eaf2051cc127123b680aa648c2c2fc00bb74385f Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Mon, 25 Jul 2022 22:37:31 +0000 Subject: SampleTIAS Create surface display In this CL, the sample TIAS responds to onSetSurface and onSurfaceChanged by creating a virtualDisplay which it fills with the new mViewContainer. This container is currently only a LinearLayout which has a red background for testing purposes, however in future CLs it will be filled with proper GUI views. Bug: 240203641 Test: Manually with Cuttlefish. After TIS calls the TIAS, should see screen entirely filled with the red background. Change-Id: I051588da3b953b3fa52aa34fda70bbcc86f5efc4 --- .../TiasSessionImpl.java | 74 +++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java index 5c499838..d0214c2a 100644 --- a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java +++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java @@ -16,14 +16,22 @@ package com.android.tv.samples.sampletvinteractiveappservice; +import android.app.Presentation; import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.hardware.display.DisplayManager; +import android.hardware.display.VirtualDisplay; import android.media.tv.TvTrackInfo; import android.media.tv.interactive.TvInteractiveAppManager; import android.media.tv.interactive.TvInteractiveAppService; import android.net.Uri; import android.os.Handler; +import android.util.DisplayMetrics; import android.util.Log; import android.view.Surface; +import android.view.ViewGroup; +import android.widget.LinearLayout; import java.util.List; @@ -31,8 +39,14 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { private static final String TAG = "SampleTvInteractiveAppService"; private static final boolean DEBUG = true; + private static final String VIRTUAL_DISPLAY_NAME = "sample_tias_display"; + + private final Context mContext; private final Handler mHandler; private final int mType; + private final ViewGroup mViewContainer; + private Surface mSurface; + private VirtualDisplay mVirtualDisplay; public TiasSessionImpl(Context context, String iAppServiceId, int type) { super(context); @@ -40,8 +54,13 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { Log.d(TAG, "Constructing service with iAppServiceId=" + iAppServiceId + " type=" + type); } + mContext = context; mType = type; mHandler = new Handler(context.getMainLooper()); + + mViewContainer = new LinearLayout(context); + // TODO: Remove temporary red background color + mViewContainer.setBackground(new ColorDrawable(Color.RED)); } @Override @@ -49,6 +68,14 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { if (DEBUG) { Log.d(TAG, "onRelease"); } + if (mSurface != null) { + mSurface.release(); + mSurface = null; + } + if (mVirtualDisplay != null) { + mVirtualDisplay.release(); + mVirtualDisplay = null; + } } @Override @@ -56,7 +83,22 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { if (DEBUG) { Log.d(TAG, "onSetSurface"); } - return false; + if (mSurface != null) { + mSurface.release(); + } + mSurface = surface; + return true; + } + + @Override + public void onSurfaceChanged(int format, int width, int height) { + if (DEBUG) { + Log.d(TAG, "onSurfaceChanged format=" + format + " width=" + width + + " height=" + height); + } + if (mSurface != null) { + updateSurface(mSurface, width, height); + } } @Override @@ -93,6 +135,36 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { 100); } + private void updateSurface(Surface surface, int width, int height) { + mHandler.post( + () -> { + // Update our virtualDisplay if it already exists, create a new one otherwise + if (mVirtualDisplay != null) { + mVirtualDisplay.setSurface(surface); + mVirtualDisplay.resize(width, height, DisplayMetrics.DENSITY_DEFAULT); + } else { + DisplayManager displayManager = + mContext.getSystemService(DisplayManager.class); + if (displayManager == null) { + Log.e(TAG, "Failed to get DisplayManager"); + return; + } + mVirtualDisplay = displayManager.createVirtualDisplay(VIRTUAL_DISPLAY_NAME, + width, + height, + DisplayMetrics.DENSITY_DEFAULT, + surface, + DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); + + Presentation presentation = + new Presentation(mContext, mVirtualDisplay.getDisplay()); + presentation.setContentView(mViewContainer); + presentation.getWindow().setBackgroundDrawable(new ColorDrawable(0)); + presentation.show(); + } + }); + } + @Override public void onCurrentChannelUri(Uri channelUri) { if (DEBUG) { -- cgit v1.2.3 From ffe47f10df6e04c424c8541ff146a4d9b7a99f45 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Thu, 28 Jul 2022 05:30:08 +0000 Subject: SampleTIAS Add initial overlay view This CL adds a graphical overlay to the TIAS on the Surface provided to it by its Manager. This overlay will be used to visually display the current value of important data fields, such as selected tracks and the current channel URI. As of this CL, the field only displays a title and the App Service ID. Bug: 240203641 Image: https://screenshot.googleplex.com/6e9f3QiZTyYWdYn.png Test: Manually using Cuttlefish, LiveTv, and SampleTunerTIS Change-Id: I36215b8abb8ce4603a4d08da409b846ed09176f4 --- .../res/layout/sample_layout.xml | 41 ++++++++++++++++++++++ .../res/values/colors.xml | 21 +++++++++++ .../res/values/strings.xml | 1 + .../res/values/styles.xml | 28 +++++++++++++++ .../TiasSessionImpl.java | 17 +++++++-- 5 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml create mode 100644 interactive/SampleTvInteractiveAppService/res/values/colors.xml create mode 100644 interactive/SampleTvInteractiveAppService/res/values/styles.xml diff --git a/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml b/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml new file mode 100644 index 00000000..0ae7cdd5 --- /dev/null +++ b/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml @@ -0,0 +1,41 @@ + + + + + + + + + + \ No newline at end of file diff --git a/interactive/SampleTvInteractiveAppService/res/values/colors.xml b/interactive/SampleTvInteractiveAppService/res/values/colors.xml new file mode 100644 index 00000000..d2a0a25a --- /dev/null +++ b/interactive/SampleTvInteractiveAppService/res/values/colors.xml @@ -0,0 +1,21 @@ + + + + + #CCCCCCCC + #FF000000 + \ No newline at end of file diff --git a/interactive/SampleTvInteractiveAppService/res/values/strings.xml b/interactive/SampleTvInteractiveAppService/res/values/strings.xml index 485b7290..ea26de66 100644 --- a/interactive/SampleTvInteractiveAppService/res/values/strings.xml +++ b/interactive/SampleTvInteractiveAppService/res/values/strings.xml @@ -22,4 +22,5 @@ ginga atsc + Sample TV Interactive App Service \ No newline at end of file diff --git a/interactive/SampleTvInteractiveAppService/res/values/styles.xml b/interactive/SampleTvInteractiveAppService/res/values/styles.xml new file mode 100644 index 00000000..d207c99e --- /dev/null +++ b/interactive/SampleTvInteractiveAppService/res/values/styles.xml @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java index d0214c2a..de5b529d 100644 --- a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java +++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java @@ -18,7 +18,6 @@ package com.android.tv.samples.sampletvinteractiveappservice; import android.app.Presentation; import android.content.Context; -import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; @@ -29,9 +28,12 @@ import android.net.Uri; import android.os.Handler; import android.util.DisplayMetrics; import android.util.Log; +import android.view.LayoutInflater; import android.view.Surface; +import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; +import android.widget.TextView; import java.util.List; @@ -43,6 +45,7 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { private final Context mContext; private final Handler mHandler; + private final String mAppServiceId; private final int mType; private final ViewGroup mViewContainer; private Surface mSurface; @@ -55,12 +58,12 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { + " type=" + type); } mContext = context; + mAppServiceId = iAppServiceId; mType = type; mHandler = new Handler(context.getMainLooper()); mViewContainer = new LinearLayout(context); - // TODO: Remove temporary red background color - mViewContainer.setBackground(new ColorDrawable(Color.RED)); + mViewContainer.setBackground(new ColorDrawable(0)); } @Override @@ -108,6 +111,7 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { } mHandler.post( () -> { + initSampleView(); requestCurrentTvInputId(); requestCurrentChannelUri(); requestTrackInfoList(); @@ -165,6 +169,13 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { }); } + private void initSampleView() { + View sampleView = LayoutInflater.from(mContext).inflate(R.layout.sample_layout, null); + TextView appServiceIdText = sampleView.findViewById(R.id.app_service_id); + appServiceIdText.setText("App Service ID: " + mAppServiceId); + mViewContainer.addView(sampleView); + } + @Override public void onCurrentChannelUri(Uri channelUri) { if (DEBUG) { -- cgit v1.2.3 From f836b31dbedb6ab54145f9c5690b76eb5e8c310e Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Thu, 28 Jul 2022 06:02:35 +0000 Subject: SampleTIAS Create visual fields for data values The SampleTIAS uses a visual overlay to display the current values of important variables. This CL creates the visual elements for the current channel uri, tv input id, and selected tracks. The first 2 values are directly set after being requested, while correctly handling the current tracks selected will be left to a future CL. Bug: 240203641 Image: https://screenshot.googleplex.com/3tFs8drvs6aZtqC.png Test: Manually with Cuttlefish, LiveTv, and SampleTunerTIS Change-Id: I8b9cbaf756aef759eeb897db9e0b6a498f4506b5 --- .../res/layout/sample_layout.xml | 15 +++++++++++++++ .../TiasSessionImpl.java | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml b/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml index 0ae7cdd5..b5e7cbe3 100644 --- a/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml +++ b/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml @@ -37,5 +37,20 @@ + + + + + \ No newline at end of file diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java index de5b529d..7f5dce34 100644 --- a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java +++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java @@ -51,6 +51,12 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { private Surface mSurface; private VirtualDisplay mVirtualDisplay; + private TextView mTvInputIdView; + private TextView mChannelUriView; + private TextView mVideoTrackView; + private TextView mAudioTrackView; + private TextView mSubtitleTrackView; + public TiasSessionImpl(Context context, String iAppServiceId, int type) { super(context); if (DEBUG) { @@ -173,6 +179,18 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { View sampleView = LayoutInflater.from(mContext).inflate(R.layout.sample_layout, null); TextView appServiceIdText = sampleView.findViewById(R.id.app_service_id); appServiceIdText.setText("App Service ID: " + mAppServiceId); + + mTvInputIdView = sampleView.findViewById(R.id.tv_input_id); + mChannelUriView = sampleView.findViewById(R.id.channel_uri); + mVideoTrackView = sampleView.findViewById(R.id.video_track_selected); + mAudioTrackView = sampleView.findViewById(R.id.audio_track_selected); + mSubtitleTrackView = sampleView.findViewById(R.id.subtitle_track_selected); + // Set default values for the selected tracks, since we cannot request data on them directly + // TODO: Implement onTrackSelected() to fill these values + mVideoTrackView.setText("No video track selected"); + mAudioTrackView.setText("No audio track selected"); + mSubtitleTrackView.setText("No subtitle track selected"); + mViewContainer.addView(sampleView); } @@ -181,6 +199,7 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { if (DEBUG) { Log.d(TAG, "onCurrentChannelUri uri=" + channelUri); } + mChannelUriView.setText("Channel URI: " + channelUri); } @Override @@ -202,5 +221,6 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { if (DEBUG) { Log.d(TAG, "onCurrentTvInputId id=" + inputId); } + mTvInputIdView.setText("TV Input ID: " + inputId); } } -- cgit v1.2.3 From e72cc781729426e5c15b8b1eb7ca67c78f0b3815 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Thu, 28 Jul 2022 06:31:26 +0000 Subject: SampleTIAS Add selected track info to overlay In the SampleTIAS, an overlay is used to display the current value of important variables. While previously we only had initial default values for the selected tracks, this CL adds their real values to the overlay. An initial default value is still needed, however, since we cannot request the data for which tracks are selected and must instead wait for it to be sent to us. Bug: 240203641 Image: https://screenshot.googleplex.com/4BYTLpuZsUkdDWu.png Test: Manually with Cuttlefish, LiveTv, and SampleTunerTIS Change-Id: I85a0ab80f56a0979b73500c035cd1f0e5dcd8235 --- .../TiasSessionImpl.java | 68 +++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java index 7f5dce34..f3ae6222 100644 --- a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java +++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java @@ -50,6 +50,7 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { private final ViewGroup mViewContainer; private Surface mSurface; private VirtualDisplay mVirtualDisplay; + private List mTracks; private TextView mTvInputIdView; private TextView mChannelUriView; @@ -186,7 +187,6 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { mAudioTrackView = sampleView.findViewById(R.id.audio_track_selected); mSubtitleTrackView = sampleView.findViewById(R.id.subtitle_track_selected); // Set default values for the selected tracks, since we cannot request data on them directly - // TODO: Implement onTrackSelected() to fill these values mVideoTrackView.setText("No video track selected"); mAudioTrackView.setText("No audio track selected"); mSubtitleTrackView.setText("No subtitle track selected"); @@ -194,6 +194,55 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { mViewContainer.addView(sampleView); } + private void updateTrackSelectedView(int type, String trackId) { + mHandler.post( + () -> { + if (mTracks == null) { + return; + } + TvTrackInfo newSelectedTrack = null; + for (TvTrackInfo track : mTracks) { + if (track.getType() == type && track.getId().equals(trackId)) { + newSelectedTrack = track; + break; + } + } + + if (newSelectedTrack == null) { + if (DEBUG) { + Log.d(TAG, "Did not find selected track within track list"); + } + return; + } + switch (newSelectedTrack.getType()) { + case TvTrackInfo.TYPE_VIDEO: + mVideoTrackView.setText( + "Video Track: id= " + newSelectedTrack.getId() + + ", height=" + newSelectedTrack.getVideoHeight() + + ", width=" + newSelectedTrack.getVideoWidth() + + ", frame_rate=" + newSelectedTrack.getVideoFrameRate() + + ", pixel_ratio=" + newSelectedTrack.getVideoPixelAspectRatio() + ); + break; + case TvTrackInfo.TYPE_AUDIO: + mAudioTrackView.setText( + "Audio Track: id=" + newSelectedTrack.getId() + + ", lang=" + newSelectedTrack.getLanguage() + + ", sample_rate=" + newSelectedTrack.getAudioSampleRate() + + ", channel_count=" + newSelectedTrack.getAudioChannelCount() + ); + break; + case TvTrackInfo.TYPE_SUBTITLE: + mSubtitleTrackView.setText( + "Subtitle Track: id=" + newSelectedTrack.getId() + + ", lang=" + newSelectedTrack.getLanguage() + ); + break; + } + } + ); + } + @Override public void onCurrentChannelUri(Uri channelUri) { if (DEBUG) { @@ -214,6 +263,23 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { } } } + mTracks = tracks; + } + + @Override + public void onTracksChanged(List tracks) { + if (DEBUG) { + Log.d(TAG, "onTracksChanged"); + } + onTrackInfoList(tracks); + } + + @Override + public void onTrackSelected(int type, String trackId) { + if (DEBUG) { + Log.d(TAG, "onTrackSelected type=" + type + " trackId=" + trackId); + } + updateTrackSelectedView(type, trackId); } @Override -- cgit v1.2.3 From b26026ad226bf433080cfc13ffd7aeaea139f715 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Tue, 26 Jul 2022 23:16:09 +0000 Subject: SampleTIAS Add tuning on button press Currently, our SampleTIAS requests data and creates an overlay for itself upon initialization. This CL adds its first piece of user interactivity by allowing the user to press the Red Interactive Button in order to tune to the next channel. A field is added to the GUI to inform the user of this option. Bug: 240578538 Image: https://screenshot.googleplex.com/3TZxVRSTfqVspar.png Test: Manually with Cuttlefish, LiveTv, and SampleTunerTIS Change-Id: Ibf34a494bbf8b610036aff5d4da04260da632af1 --- .../res/layout/sample_layout.xml | 7 ++++ .../res/values/strings.xml | 1 + .../TiasSessionImpl.java | 38 ++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml b/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml index b5e7cbe3..d8659f9b 100644 --- a/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml +++ b/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml @@ -34,6 +34,13 @@ android:text="@string/overlay_title_string" android:textColor="@color/overlay_text_color" android:textSize="32sp"/> + + diff --git a/interactive/SampleTvInteractiveAppService/res/values/strings.xml b/interactive/SampleTvInteractiveAppService/res/values/strings.xml index ea26de66..d0c33d7f 100644 --- a/interactive/SampleTvInteractiveAppService/res/values/strings.xml +++ b/interactive/SampleTvInteractiveAppService/res/values/strings.xml @@ -23,4 +23,5 @@ atsc Sample TV Interactive App Service + Press the Red Interactive Button to tune to the next channel \ No newline at end of file diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java index f3ae6222..83b16ffc 100644 --- a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java +++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java @@ -28,6 +28,7 @@ import android.net.Uri; import android.os.Handler; import android.util.DisplayMetrics; import android.util.Log; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Surface; import android.view.View; @@ -35,6 +36,8 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; + import java.util.List; public class TiasSessionImpl extends TvInteractiveAppService.Session { @@ -146,6 +149,27 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { 100); } + @Override + public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_PROG_RED: + tuneToNextChannel(); + return true; + default: + return super.onKeyDown(keyCode, event); + } + } + + @Override + public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_PROG_RED: + return true; + default: + return super.onKeyUp(keyCode, event); + } + } + private void updateSurface(Surface surface, int width, int height) { mHandler.post( () -> { @@ -243,6 +267,20 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session { ); } + private void tuneToNextChannel() { + sendPlaybackCommandRequest(TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE_NEXT, + null); + // Delay request for new information to give time to tune + mHandler.postDelayed( + () -> { + requestCurrentTvInputId(); + requestCurrentChannelUri(); + requestTrackInfoList(); + }, + 1000 + ); + } + @Override public void onCurrentChannelUri(Uri channelUri) { if (DEBUG) { -- cgit v1.2.3 From e4380ee431303ee45a964b84e1d8d0eeee040634 Mon Sep 17 00:00:00 2001 From: Sadiq Sada Date: Sun, 7 Aug 2022 20:12:08 +0000 Subject: Update ChannelTuner description Minor update to ChannelTuner description to understand code workflow Test: None Change-Id: I0a19d13bca44c9e9d23c8b5fbda229a9b7014a45 --- src/com/android/tv/ChannelTuner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/tv/ChannelTuner.java b/src/com/android/tv/ChannelTuner.java index fe138980..351f0010 100644 --- a/src/com/android/tv/ChannelTuner.java +++ b/src/com/android/tv/ChannelTuner.java @@ -36,7 +36,7 @@ import java.util.Map; import java.util.Set; /** - * It manages the current tuned channel among browsable channels. And it determines the next channel + * Manages the current tuned channel among browsable channels, and determines the next channel * by channel up/down. But, it doesn't actually tune through TvView. */ @MainThread -- cgit v1.2.3 From 06f26aaedf9a6b179ae68614522d734e0d1b7198 Mon Sep 17 00:00:00 2001 From: qingxun Date: Thu, 4 Aug 2022 22:40:19 +0000 Subject: Add Interactive App Settings Fragment Bug: 241110408 Test: Manually on cuttlefish. Should see Settings Fragment by going into the LiveTv app -> Settings -> Interactive App Settings Change-Id: Id2f2138def6b2ffbdb7b25442756b1fb5406608d --- res/values/strings.xml | 5 ++ .../sidepanel/InteractiveAppSettingsFragment.java | 54 ++++++++++++++++++++++ .../android/tv/ui/sidepanel/SettingsFragment.java | 12 +++++ src/com/android/tv/util/TvSettings.java | 15 ++++++ 4 files changed, 86 insertions(+) create mode 100755 src/com/android/tv/ui/sidepanel/InteractiveAppSettingsFragment.java diff --git a/res/values/strings.xml b/res/values/strings.xml index e272244d..ad469135 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1044,4 +1044,9 @@ Selecting \"Allow\", enables Live TV to immediately free storage space when deleting recorded TV programs. This makes more space available for new recordings. + + + Interactive app settings + On + Off diff --git a/src/com/android/tv/ui/sidepanel/InteractiveAppSettingsFragment.java b/src/com/android/tv/ui/sidepanel/InteractiveAppSettingsFragment.java new file mode 100755 index 00000000..b56a1d66 --- /dev/null +++ b/src/com/android/tv/ui/sidepanel/InteractiveAppSettingsFragment.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 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.ui.sidepanel; + +import com.android.tv.R; +import com.android.tv.util.TvSettings; +import java.util.ArrayList; +import java.util.List; + +public class InteractiveAppSettingsFragment extends SideFragment { + private static final String TRACKER_LABEL = "Interactive Application Settings"; + @Override + protected String getTitle() { + return getString(R.string.interactive_app_settings); + } + @Override + public String getTrackerLabel() { + return TRACKER_LABEL; + } + @Override + protected List getItemList() { + List items = new ArrayList<>(); + items.add( + new SwitchItem( + getString(R.string.tv_iapp_on), + getString(R.string.tv_iapp_off)) { + @Override + protected void onUpdate() { + super.onUpdate(); + setChecked(TvSettings.isTvIAppOn(getContext())); + } + @Override + protected void onSelected() { + super.onSelected(); + boolean checked = isChecked(); + TvSettings.setTvIAppOn(getContext(), checked); + } + }); + return items; + } +} diff --git a/src/com/android/tv/ui/sidepanel/SettingsFragment.java b/src/com/android/tv/ui/sidepanel/SettingsFragment.java index 1c03b6a9..e1ad9d45 100644 --- a/src/com/android/tv/ui/sidepanel/SettingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/SettingsFragment.java @@ -190,6 +190,18 @@ public class SettingsFragment extends SideFragment { } }); } + //Interactive Application Settings + items.add( + new ActionItem(getString(R.string.interactive_app_settings)) { + @Override + protected void onSelected() { + getMainActivity() + .getOverlayManager() + .getSideFragmentManager() + .show(new InteractiveAppSettingsFragment(), false); + } + }); + // Show version. SimpleActionItem version = new SimpleActionItem( diff --git a/src/com/android/tv/util/TvSettings.java b/src/com/android/tv/util/TvSettings.java index ae79e7e5..1a5434cb 100644 --- a/src/com/android/tv/util/TvSettings.java +++ b/src/com/android/tv/util/TvSettings.java @@ -53,6 +53,9 @@ public final class TvSettings { private static final String PREF_CONTENT_RATING_LEVEL = "pref.content_rating_level"; private static final String PREF_DISABLE_PIN_UNTIL = "pref.disable_pin_until"; + // tviapp settings + private static final String PREF_TV_IAPP_STATES = "pref.tviapp_on"; + @Retention(RetentionPolicy.SOURCE) @IntDef({ CONTENT_RATING_LEVEL_NONE, @@ -242,4 +245,16 @@ public final class TvSettings { .putLong(PREF_DISABLE_PIN_UNTIL, timeMillis) .apply(); } + + public static boolean isTvIAppOn(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(PREF_TV_IAPP_STATES, false); + } + + public static void setTvIAppOn(Context context, boolean isOn) { + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putBoolean(PREF_TV_IAPP_STATES, isOn) + .apply(); + } } -- cgit v1.2.3 From 2c08af6b67831b4d58200370b6f44c13cc541983 Mon Sep 17 00:00:00 2001 From: qingxun Date: Wed, 10 Aug 2022 01:08:58 +0000 Subject: Add TIAF Feature Flag Bug: 241110408 Test: Manually on cuttlefish Change-Id: I4dd736cb17142db728e36b0a837a8adaf8044bd5 --- common/src/com/android/tv/common/feature/Sdk.java | 2 ++ src/com/android/tv/features/TvFeatures.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/common/src/com/android/tv/common/feature/Sdk.java b/common/src/com/android/tv/common/feature/Sdk.java index e59bcd60..bf76c9ce 100644 --- a/common/src/com/android/tv/common/feature/Sdk.java +++ b/common/src/com/android/tv/common/feature/Sdk.java @@ -27,6 +27,8 @@ public final class Sdk { public static final Feature AT_LEAST_O = new AtLeast(VERSION_CODES.O); + public static final Feature AT_LEAST_T = new AtLeast(VERSION_CODES.TIRAMISU); + private static final class AtLeast implements Feature { private final int versionCode; diff --git a/src/com/android/tv/features/TvFeatures.java b/src/com/android/tv/features/TvFeatures.java index 5282c28c..ebd7cb9a 100644 --- a/src/com/android/tv/features/TvFeatures.java +++ b/src/com/android/tv/features/TvFeatures.java @@ -101,5 +101,8 @@ public final class TvFeatures extends CommonFeatures { /** Use input blocklist to disable partner's tuner input. */ public static final Feature USE_PARTNER_INPUT_BLOCKLIST = ON; + /** Support for interactive applications using the TIAF **/ + public static final Feature HAS_TIAF = Sdk.AT_LEAST_T; + private TvFeatures() {} } -- cgit v1.2.3 From df79eb31f11576ad837843ee3efad9ec97e949c3 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Mon, 1 Aug 2022 20:06:47 +0000 Subject: LiveTv TIAS Integration Create manager and view This CL begins the process of adding TIAS integration into our LiveTv app by adding and initializing TvInteractiveApp managers and views, as well as creating callbacks to be filled out in future CLs. Bug: 241110408 Test: Manual with Cuttlefish Change-Id: Ia569048fb70565b68c93a7bc4c415055fe8d7140 --- res/layout/activity_tv.xml | 6 ++ src/com/android/tv/MainActivity.java | 6 ++ src/com/android/tv/interactive/IAppManager.java | 120 ++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 src/com/android/tv/interactive/IAppManager.java diff --git a/res/layout/activity_tv.xml b/res/layout/activity_tv.xml index b6a0a3a3..6347f897 100644 --- a/res/layout/activity_tv.xml +++ b/res/layout/activity_tv.xml @@ -28,6 +28,12 @@ android:layout_height="match_parent" android:layout_gravity="start|center_vertical" /> + + mOnActionClickListeners = new ArraySet<>(); @@ -732,6 +735,9 @@ public class MainActivity extends Activity mDvrConflictChecker = new ConflictChecker(this); } initForTest(); + if (TvFeatures.HAS_TIAF.isEnabled(this)) { + mIAppManager = new IAppManager(this); + } Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onCreate end"); } diff --git a/src/com/android/tv/interactive/IAppManager.java b/src/com/android/tv/interactive/IAppManager.java new file mode 100644 index 00000000..b3061bc6 --- /dev/null +++ b/src/com/android/tv/interactive/IAppManager.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 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.interactive; + +import android.annotation.TargetApi; +import android.graphics.Rect; +import android.media.tv.interactive.TvInteractiveAppManager; +import android.media.tv.interactive.TvInteractiveAppView; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; + +import com.android.tv.MainActivity; +import com.android.tv.R; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.features.TvFeatures; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@TargetApi(Build.VERSION_CODES.TIRAMISU) +public class IAppManager { + private static final String TAG = "IAppManager"; + + private final MainActivity mMainActivity; + private final TvInteractiveAppManager mTvIAppManager; + private final TvInteractiveAppView mTvIAppView; + + public IAppManager(MainActivity parentActivity) { + SoftPreconditions.checkFeatureEnabled(parentActivity, TvFeatures.HAS_TIAF, TAG); + + mMainActivity = parentActivity; + mTvIAppManager = mMainActivity.getSystemService(TvInteractiveAppManager.class); + mTvIAppView = mMainActivity.findViewById(R.id.tv_app_view); + if (mTvIAppManager == null || mTvIAppView == null) { + Log.e(TAG, "Could not find interactive app view or manager"); + return; + } + + ExecutorService executor = Executors.newSingleThreadExecutor(); + mTvIAppManager.registerCallback( + executor, + new MyInteractiveAppManagerCallback() + ); + mTvIAppView.setCallback( + executor, + new MyInteractiveAppViewCallback() + ); + } + + private class MyInteractiveAppManagerCallback extends + TvInteractiveAppManager.TvInteractiveAppCallback { + @Override + public void onInteractiveAppServiceAdded(String iAppServiceId) {} + + @Override + public void onInteractiveAppServiceRemoved(String iAppServiceId) {} + + @Override + public void onInteractiveAppServiceUpdated(String iAppServiceId) {} + + @Override + public void onTvInteractiveAppServiceStateChanged(String iAppServiceId, int type, int state, + int err) {} + } + + private class MyInteractiveAppViewCallback extends + TvInteractiveAppView.TvInteractiveAppCallback { + @Override + public void onPlaybackCommandRequest(String iAppServiceId, String cmdType, + Bundle parameters) {} + + @Override + public void onStateChanged(String iAppServiceId, int state, int err) {} + + @Override + public void onBiInteractiveAppCreated(String iAppServiceId, Uri biIAppUri, + String biIAppId) {} + + @Override + public void onTeletextAppStateChanged(String iAppServiceId, int state) {} + + @Override + public void onSetVideoBounds(String iAppServiceId, Rect rect) {} + + @Override + public void onRequestCurrentChannelUri(String iAppServiceId) {} + + @Override + public void onRequestCurrentChannelLcn(String iAppServiceId) {} + + @Override + public void onRequestStreamVolume(String iAppServiceId) {} + + @Override + public void onRequestTrackInfoList(String iAppServiceId) {} + + @Override + public void onRequestCurrentTvInputId(String iAppServiceId) {} + + @Override + public void onRequestSigning(String iAppServiceId, String signingId, String algorithm, + String alias, byte[] data) {} + } +} -- cgit v1.2.3 From 710ce5a55205cbd242b90c30fa2f04c53874732e Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 3 Aug 2022 20:51:40 +0000 Subject: LiveTv TIAS Integration Send AIT info to MainActivity For our LiveTv integration, the TIAS is opened after our TIS receives an Application Information Table and calls notifyAitInfoUpdated(). This CL changes our TunableTvView to bring the aitInfoUpdated notification from our TIS to the LiveTv's MainActivity. Bug: 241110408 Test: Manually with Cuttlefish Change-Id: I1e41c5e5dc621a2d9c39f5220ea71744b48abbf2 --- src/com/android/tv/InputSessionManager.java | 7 +++++++ src/com/android/tv/MainActivity.java | 8 ++++++++ .../tv/audiotvservice/AudioOnlyTvService.java | 7 +++++++ src/com/android/tv/ui/TunableTvView.java | 24 ++++++++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/src/com/android/tv/InputSessionManager.java b/src/com/android/tv/InputSessionManager.java index ea17751b..57fc883e 100644 --- a/src/com/android/tv/InputSessionManager.java +++ b/src/com/android/tv/InputSessionManager.java @@ -18,6 +18,7 @@ package com.android.tv; import android.annotation.TargetApi; import android.content.Context; +import android.media.tv.AitInfo; import android.media.tv.TvContentRating; import android.media.tv.TvInputInfo; import android.media.tv.TvTrackInfo; @@ -582,6 +583,12 @@ public class InputSessionManager { public void onSignalStrength(String inputId, int value) { mDelegate.onSignalStrength(inputId, value); } + + @TargetApi(Build.VERSION_CODES.TIRAMISU) + @Override + public void onAitInfoUpdated(String inputId, AitInfo aitInfo) { + mDelegate.onAitInfoUpdated(inputId, aitInfo); + } } /** Called when the {@link TvView} channel is changed. */ diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java index 4eb38c68..35a5550f 100644 --- a/src/com/android/tv/MainActivity.java +++ b/src/com/android/tv/MainActivity.java @@ -18,6 +18,7 @@ package com.android.tv; import static com.android.tv.common.feature.SystemAppFeature.SYSTEM_APP_FEATURE; +import android.annotation.TargetApi; import android.app.Activity; import android.app.PendingIntent; import android.app.SearchManager; @@ -32,6 +33,7 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.Cursor; import android.hardware.display.DisplayManager; +import android.media.tv.AitInfo; import android.media.tv.TvContentRating; import android.media.tv.TvContract; import android.media.tv.TvContract.Channels; @@ -2995,6 +2997,12 @@ public class MainActivity extends Activity TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_SIGNAL_STRENGTH); } } + + @TargetApi(Build.VERSION_CODES.TIRAMISU) + @Override + public void onAitInfoUpdated(String inputId, AitInfo aitInfo) { + // TODO: Begin the Interactive App + } } private class MySingletonsImpl implements MySingletons { diff --git a/src/com/android/tv/audiotvservice/AudioOnlyTvService.java b/src/com/android/tv/audiotvservice/AudioOnlyTvService.java index 5d0e9c82..59e2406f 100644 --- a/src/com/android/tv/audiotvservice/AudioOnlyTvService.java +++ b/src/com/android/tv/audiotvservice/AudioOnlyTvService.java @@ -15,11 +15,14 @@ */ package com.android.tv.audiotvservice; +import android.annotation.TargetApi; import android.app.Notification; import android.app.Service; import android.content.Intent; import android.media.session.MediaSession; +import android.media.tv.AitInfo; import android.net.Uri; +import android.os.Build; import android.os.IBinder; import android.support.annotation.Nullable; import android.util.Log; @@ -99,4 +102,8 @@ public class AudioOnlyTvService extends Service implements OnTuneListener { @Override public void onChannelSignalStrength() {} + + @TargetApi(Build.VERSION_CODES.TIRAMISU) + @Override + public void onAitInfoUpdated(String inputId, AitInfo aitInfo) {} } diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java index a736e79d..d402b205 100644 --- a/src/com/android/tv/ui/TunableTvView.java +++ b/src/com/android/tv/ui/TunableTvView.java @@ -19,6 +19,7 @@ package com.android.tv.ui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; @@ -28,6 +29,7 @@ import android.graphics.PorterDuff; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.media.PlaybackParams; +import android.media.tv.AitInfo; import android.media.tv.TvContentRating; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; @@ -413,6 +415,25 @@ public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvV mOnTuneListener.onChannelSignalStrength(); } } + + @TargetApi(Build.VERSION_CODES.TIRAMISU) + @Override + public void onAitInfoUpdated(String inputId, AitInfo aitInfo) { + if (!TvFeatures.HAS_TIAF.isEnabled(getContext())) { + return; + } + if (DEBUG) { + Log.d(TAG, + "onAitInfoUpdated: {inputId=" + + inputId + + ", AitInfo=(" + + aitInfo + +")}"); + } + if (mOnTuneListener != null) { + mOnTuneListener.onAitInfoUpdated(inputId, aitInfo); + } + } }; public TunableTvView(Context context) { @@ -773,6 +794,9 @@ public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvV void onContentAllowed(); void onChannelSignalStrength(); + + @TargetApi(Build.VERSION_CODES.TIRAMISU) + void onAitInfoUpdated(String inputId, AitInfo aitInfo); } public void unblockContent(TvContentRating rating) { -- cgit v1.2.3 From 2b7cf6ecffceee455295a359bc78cf3a62219545 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Tue, 2 Aug 2022 07:30:39 +0000 Subject: LiveTv TIAS Integration Start and stop TIAS Currently, the LiveTv is able to receive AitInfoUpdated notifications from the TIS. This CL has the LiveTv actually start the TIAS once it receives these notifications, and stop it once the LiveTv app is stopped. Bug: 241110408 Test: Manually on Cuttlefish. Should see TIAS start. Change-Id: I12aa5973427a2ae1aa817d14f9c5d6e38f52ab48 --- src/com/android/tv/MainActivity.java | 9 ++- src/com/android/tv/interactive/IAppManager.java | 80 ++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java index 35a5550f..8bfa05b8 100644 --- a/src/com/android/tv/MainActivity.java +++ b/src/com/android/tv/MainActivity.java @@ -738,7 +738,7 @@ public class MainActivity extends Activity } initForTest(); if (TvFeatures.HAS_TIAF.isEnabled(this)) { - mIAppManager = new IAppManager(this); + mIAppManager = new IAppManager(this, mTvView); } Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onCreate end"); } @@ -1134,6 +1134,9 @@ public class MainActivity extends Activity private void stopAll(boolean keepVisibleBehind) { mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION); stopTv("stopAll()", keepVisibleBehind); + if (mIAppManager != null) { + mIAppManager.stop(); + } } public TvInputManagerHelper getTvInputManagerHelper() { @@ -3001,7 +3004,9 @@ public class MainActivity extends Activity @TargetApi(Build.VERSION_CODES.TIRAMISU) @Override public void onAitInfoUpdated(String inputId, AitInfo aitInfo) { - // TODO: Begin the Interactive App + if (mIAppManager != null) { + mIAppManager.onAitInfoUpdated(aitInfo); + } } } diff --git a/src/com/android/tv/interactive/IAppManager.java b/src/com/android/tv/interactive/IAppManager.java index b3061bc6..7d8069c5 100644 --- a/src/com/android/tv/interactive/IAppManager.java +++ b/src/com/android/tv/interactive/IAppManager.java @@ -19,32 +19,41 @@ package com.android.tv.interactive; import android.annotation.TargetApi; import android.graphics.Rect; import android.media.tv.interactive.TvInteractiveAppManager; +import android.media.tv.AitInfo; +import android.media.tv.interactive.TvInteractiveAppServiceInfo; import android.media.tv.interactive.TvInteractiveAppView; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; +import android.view.View; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.common.SoftPreconditions; import com.android.tv.features.TvFeatures; +import com.android.tv.ui.TunableTvView; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @TargetApi(Build.VERSION_CODES.TIRAMISU) public class IAppManager { private static final String TAG = "IAppManager"; + private static final boolean DEBUG = false; private final MainActivity mMainActivity; private final TvInteractiveAppManager mTvIAppManager; private final TvInteractiveAppView mTvIAppView; + private final TunableTvView mTvView; + private AitInfo mCurrentAitInfo; - public IAppManager(MainActivity parentActivity) { + public IAppManager(MainActivity parentActivity, TunableTvView tvView) { SoftPreconditions.checkFeatureEnabled(parentActivity, TvFeatures.HAS_TIAF, TAG); mMainActivity = parentActivity; + mTvView = tvView; mTvIAppManager = mMainActivity.getSystemService(TvInteractiveAppManager.class); mTvIAppView = mMainActivity.findViewById(R.id.tv_app_view); if (mTvIAppManager == null || mTvIAppView == null) { @@ -63,6 +72,65 @@ public class IAppManager { ); } + public void stop() { + mTvIAppView.stopInteractiveApp(); + mTvIAppView.reset(); + mCurrentAitInfo = null; + } + + public void onAitInfoUpdated(AitInfo aitInfo) { + if (mTvIAppManager == null || aitInfo == null) { + return; + } + if (mCurrentAitInfo != null && mCurrentAitInfo.getType() == aitInfo.getType()) { + if (DEBUG) { + Log.d(TAG, "Ignoring AIT update: Same type as current"); + } + return; + } + + List tvIAppInfoList = + mTvIAppManager.getTvInteractiveAppServiceList(); + if (tvIAppInfoList.isEmpty()) { + if (DEBUG) { + Log.d(TAG, "Ignoring AIT update: No interactive app services registered"); + } + return; + } + + // App Type ID numbers allocated by DVB Services + int type = -1; + switch (aitInfo.getType()) { + case 0x0010: // HBBTV + type = TvInteractiveAppServiceInfo.INTERACTIVE_APP_TYPE_HBBTV; + break; + case 0x0006: // DCAP-J: DCAP Java applications + case 0x0007: // DCAP-X: DCAP XHTML applications + type = TvInteractiveAppServiceInfo.INTERACTIVE_APP_TYPE_ATSC; + break; + case 0x0001: // Ginga-J + case 0x0009: // Ginga-NCL + case 0x000b: // Ginga-HTML5 + type = TvInteractiveAppServiceInfo.INTERACTIVE_APP_TYPE_GINGA; + break; + default: + Log.e(TAG, "AIT info contained unknown type: " + aitInfo.getType()); + return; + } + + // TODO: Only open interactive app if enabled through settings + for (TvInteractiveAppServiceInfo info : tvIAppInfoList) { + if ((info.getSupportedTypes() & type) > 0) { + mCurrentAitInfo = aitInfo; + if (mTvIAppView != null) { + mTvIAppView.setVisibility(View.VISIBLE); + mTvIAppView.prepareInteractiveApp(info.getId(), type); + } + break; + } + } + } + private class MyInteractiveAppManagerCallback extends TvInteractiveAppManager.TvInteractiveAppCallback { @Override @@ -76,7 +144,15 @@ public class IAppManager { @Override public void onTvInteractiveAppServiceStateChanged(String iAppServiceId, int type, int state, - int err) {} + int err) { + if (state == TvInteractiveAppManager.SERVICE_STATE_READY && mTvIAppView != null) { + mTvIAppView.startInteractiveApp(); + mTvIAppView.setTvView(mTvView.getTvView()); + if (mTvView.getTvView() != null) { + mTvView.getTvView().setInteractiveAppNotificationEnabled(true); + } + } + } } private class MyInteractiveAppViewCallback extends -- cgit v1.2.3 From 607799d39fa5a596884fed5e6c70a3f9abbb8cfa Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 3 Aug 2022 21:53:27 +0000 Subject: LiveTv TIAS Integration ViewCallback data requests The TvInteractiveAppView.TvInteractiveAppCallback contains many methods which act as requests for data from the application which the TIAS is currently running on top of. This CL implements these callback methods for the LiveTv MainActivity. Bug: 241110408 Test: Manually with Cuttlefish and SampleTIAS Change-Id: I46406c961206407ed49d006dca5bcdefcfe55419 --- src/com/android/tv/data/StreamInfo.java | 2 + src/com/android/tv/interactive/IAppManager.java | 74 +++++++++++++++++++++++-- src/com/android/tv/ui/TunableTvView.java | 7 +++ 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/com/android/tv/data/StreamInfo.java b/src/com/android/tv/data/StreamInfo.java index e4237bf4..f323423c 100644 --- a/src/com/android/tv/data/StreamInfo.java +++ b/src/com/android/tv/data/StreamInfo.java @@ -44,6 +44,8 @@ public interface StreamInfo { int getAudioChannelCount(); + float getStreamVolume(); + boolean hasClosedCaption(); boolean isVideoAvailable(); diff --git a/src/com/android/tv/interactive/IAppManager.java b/src/com/android/tv/interactive/IAppManager.java index 7d8069c5..9d78515c 100644 --- a/src/com/android/tv/interactive/IAppManager.java +++ b/src/com/android/tv/interactive/IAppManager.java @@ -18,6 +18,7 @@ package com.android.tv.interactive; import android.annotation.TargetApi; import android.graphics.Rect; +import android.media.tv.TvTrackInfo; import android.media.tv.interactive.TvInteractiveAppManager; import android.media.tv.AitInfo; import android.media.tv.interactive.TvInteractiveAppServiceInfo; @@ -31,9 +32,11 @@ import android.view.View; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.common.SoftPreconditions; +import com.android.tv.data.api.Channel; import com.android.tv.features.TvFeatures; import com.android.tv.ui.TunableTvView; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -175,19 +178,80 @@ public class IAppManager { public void onSetVideoBounds(String iAppServiceId, Rect rect) {} @Override - public void onRequestCurrentChannelUri(String iAppServiceId) {} + public void onRequestCurrentChannelUri(String iAppServiceId) { + if (mTvIAppView == null) { + return; + } + Channel currentChannel = mMainActivity.getCurrentChannel(); + Uri currentUri = (currentChannel == null) + ? null + : currentChannel.getUri(); + mTvIAppView.sendCurrentChannelUri(currentUri); + } @Override - public void onRequestCurrentChannelLcn(String iAppServiceId) {} + public void onRequestCurrentChannelLcn(String iAppServiceId) { + if (mTvIAppView == null) { + return; + } + Channel currentChannel = mMainActivity.getCurrentChannel(); + if (currentChannel == null || currentChannel.getDisplayNumber() == null) { + return; + } + // Expected format is major channel number, delimiter, minor channel number + String displayNumber = currentChannel.getDisplayNumber(); + String format = "[0-9]+" + Channel.CHANNEL_NUMBER_DELIMITER + "[0-9]+"; + if (!displayNumber.matches(format)) { + return; + } + // Major channel number is returned + String[] numbers = displayNumber.split( + String.valueOf(Channel.CHANNEL_NUMBER_DELIMITER)); + mTvIAppView.sendCurrentChannelLcn(Integer.parseInt(numbers[0])); + } @Override - public void onRequestStreamVolume(String iAppServiceId) {} + public void onRequestStreamVolume(String iAppServiceId) { + if (mTvIAppView == null || mTvView == null) { + return; + } + mTvIAppView.sendStreamVolume(mTvView.getStreamVolume()); + } @Override - public void onRequestTrackInfoList(String iAppServiceId) {} + public void onRequestTrackInfoList(String iAppServiceId) { + if (mTvIAppView == null || mTvView == null) { + return; + } + List allTracks = new ArrayList<>(); + int[] trackTypes = new int[] {TvTrackInfo.TYPE_AUDIO, + TvTrackInfo.TYPE_VIDEO, TvTrackInfo.TYPE_SUBTITLE}; + + for (int trackType : trackTypes) { + List currentTracks = mTvView.getTracks(trackType); + if (currentTracks == null) { + continue; + } + for (TvTrackInfo track : currentTracks) { + if (track != null) { + allTracks.add(track); + } + } + } + mTvIAppView.sendTrackInfoList(allTracks); + } @Override - public void onRequestCurrentTvInputId(String iAppServiceId) {} + public void onRequestCurrentTvInputId(String iAppServiceId) { + if (mTvIAppView == null) { + return; + } + Channel currentChannel = mMainActivity.getCurrentChannel(); + String currentInputId = (currentChannel == null) + ? null + : currentChannel.getInputId(); + mTvIAppView.sendCurrentTvInputId(currentInputId); + } @Override public void onRequestSigning(String iAppServiceId, String signingId, String algorithm, diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java index d402b205..0ccaad3a 100644 --- a/src/com/android/tv/ui/TunableTvView.java +++ b/src/com/android/tv/ui/TunableTvView.java @@ -736,6 +736,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvV } } + @Override + public float getStreamVolume() { + return mIsMuted + ? 0 + : mVolume; + } + /** * Sets fixed size for the internal {@link android.view.Surface} of {@link * android.media.tv.TvView}. If either {@code width} or {@code height} is non positive, the -- cgit v1.2.3 From c36926be70df3fec7cb552bccc66f18f61352824 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Fri, 5 Aug 2022 03:10:41 +0000 Subject: LiveTv TIAS Integration playbackCommandRequest The TvInteractiveAppView.TvInteractiveAppCallback includes a playbackCommandRequest method which allows the TIAS to request changes to be made on the application running it. This CL implements all of these commands except track selection, which will be implemented in a future CL alongside changes to the audio system needed to support it. Bug: 241110408 Test: Manually with Cuttlefish and SampleTIAS Change-Id: I6e9f9065dd28dad4451150daa5fe5deb5b3b38d1 --- src/com/android/tv/MainActivity.java | 4 +- src/com/android/tv/interactive/IAppManager.java | 58 ++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java index 8bfa05b8..2ba28965 100644 --- a/src/com/android/tv/MainActivity.java +++ b/src/com/android/tv/MainActivity.java @@ -738,7 +738,7 @@ public class MainActivity extends Activity } initForTest(); if (TvFeatures.HAS_TIAF.isEnabled(this)) { - mIAppManager = new IAppManager(this, mTvView); + mIAppManager = new IAppManager(this, mTvView, mHandler); } Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onCreate end"); } @@ -1642,7 +1642,7 @@ public class MainActivity extends Activity } } - private void stopTv() { + public void stopTv() { stopTv(null, false); } diff --git a/src/com/android/tv/interactive/IAppManager.java b/src/com/android/tv/interactive/IAppManager.java index 9d78515c..f2495cc9 100644 --- a/src/com/android/tv/interactive/IAppManager.java +++ b/src/com/android/tv/interactive/IAppManager.java @@ -21,17 +21,21 @@ import android.graphics.Rect; import android.media.tv.TvTrackInfo; import android.media.tv.interactive.TvInteractiveAppManager; import android.media.tv.AitInfo; +import android.media.tv.interactive.TvInteractiveAppService; import android.media.tv.interactive.TvInteractiveAppServiceInfo; import android.media.tv.interactive.TvInteractiveAppView; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; import android.util.Log; import android.view.View; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.util.ContentUriUtils; import com.android.tv.data.api.Channel; import com.android.tv.features.TvFeatures; import com.android.tv.ui.TunableTvView; @@ -50,13 +54,16 @@ public class IAppManager { private final TvInteractiveAppManager mTvIAppManager; private final TvInteractiveAppView mTvIAppView; private final TunableTvView mTvView; + private final Handler mHandler; private AitInfo mCurrentAitInfo; - public IAppManager(MainActivity parentActivity, TunableTvView tvView) { + public IAppManager(@NonNull MainActivity parentActivity, @NonNull TunableTvView tvView, + @NonNull Handler handler) { SoftPreconditions.checkFeatureEnabled(parentActivity, TvFeatures.HAS_TIAF, TAG); mMainActivity = parentActivity; mTvView = tvView; + mHandler = handler; mTvIAppManager = mMainActivity.getSystemService(TvInteractiveAppManager.class); mTvIAppView = mMainActivity.findViewById(R.id.tv_app_view); if (mTvIAppManager == null || mTvIAppView == null) { @@ -162,7 +169,54 @@ public class IAppManager { TvInteractiveAppView.TvInteractiveAppCallback { @Override public void onPlaybackCommandRequest(String iAppServiceId, String cmdType, - Bundle parameters) {} + Bundle parameters) { + if (mTvView == null) { + return; + } + switch (cmdType) { + case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE: + if (parameters == null) { + return; + } + String uriString = parameters.getString( + TvInteractiveAppService.COMMAND_PARAMETER_KEY_CHANNEL_URI); + if (uriString != null) { + Uri channelUri = Uri.parse(uriString); + Channel channel = mMainActivity.getChannelDataManager().getChannel( + ContentUriUtils.safeParseId(channelUri)); + if (channel != null) { + mHandler.post(() -> mMainActivity.tuneToChannel(channel)); + } + } + break; + case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_SELECT_TRACK: + // TODO: Handle select track command + break; + case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME: + if (parameters == null) { + return; + } + float volume = parameters.getFloat( + TvInteractiveAppService.COMMAND_PARAMETER_KEY_VOLUME, -1); + if (volume >= 0.0 && volume <= 1.0) { + mHandler.post(() -> mTvView.setStreamVolume(volume)); + } + break; + case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE_NEXT: + mHandler.post(mMainActivity::channelUp); + break; + case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE_PREV: + mHandler.post(mMainActivity::channelDown); + break; + case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_STOP: + mHandler.post(mMainActivity::stopTv); + break; + default: + Log.e(TAG, "PlaybackCommandRequest had unknown cmdType:" + + cmdType); + break; + } + } @Override public void onStateChanged(String iAppServiceId, int state, int err) {} -- cgit v1.2.3 From eacc8ff49876e7a1880b2c45df29ab7098e86e95 Mon Sep 17 00:00:00 2001 From: Lucas Gates Date: Wed, 17 Aug 2022 06:13:23 +0000 Subject: SampleTunerTIS Correct notifyVideoAvailable use The notifyVideoAvailable() function of the TIS is used to signal that the content rendered onto its surface is ready to be viewed, and must be called at least once per call to onTune(). After it is called, the manager will assume that the video is still available until notiftVideoUnavailable() is called. Our current implementation of the SampleTunerTIS calls notifyVideoAvailable() for each frame of video played. This CL changes the behavior to only call the method once per call to onTune, as well as notifying the video as unavailable while we do our setting up/tuning. Bug: 242907861 Test: Manually with Cuttlefish and LiveTv. Change-Id: I9d8d623a9b8e8fe1c3199672cfcd57599a7f572b --- .../sampletunertvinput/SampleTunerTvInputService.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 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 34e0e573..f8dfef73 100644 --- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java +++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java @@ -1,5 +1,6 @@ package com.android.tv.samples.sampletunertvinput; +import static android.media.tv.TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING; import static android.media.tv.TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN; import android.content.ContentUris; @@ -98,6 +99,7 @@ public class SampleTunerTvInputService extends TvInputService { private List mSavedData; private long mCurrentLoopStartTimeUs = 0; private long mLastFramePtsUs = 0; + private boolean mVideoAvailable; private boolean mDataReady = false; @@ -167,6 +169,9 @@ public class SampleTunerTvInputService extends TvInputService { } mChannelUri = uri; mHandler = new Handler(); + mVideoAvailable = false; + notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_TUNING); + mDecoderThread = new Thread( this::decodeInternal, @@ -271,6 +276,7 @@ public class SampleTunerTvInputService extends TvInputService { if (mMediaCodec == null) { Log.e(TAG, "null codec!"); + mVideoAvailable = false; notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_UNKNOWN); return false; } @@ -430,9 +436,12 @@ public class SampleTunerTvInputService extends TvInputService { } } mMediaCodec.releaseOutputBuffer(res, true); - notifyVideoAvailable(); - if (DEBUG) { - Log.d(TAG, "notifyVideoAvailable"); + if (!mVideoAvailable) { + mVideoAvailable = true; + notifyVideoAvailable(); + if (DEBUG) { + Log.d(TAG, "notifyVideoAvailable"); + } } } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { MediaFormat format = mMediaCodec.getOutputFormat(); -- cgit v1.2.3 From 826da4c70043fc3cce1b9238213c6dbd98bf33ce Mon Sep 17 00:00:00 2001 From: qingxun Date: Tue, 16 Aug 2022 16:00:53 -0700 Subject: Add IApp Dialog for IApp toggle Bug: 241110408 Test: Cuttlefish https://screenshot.googleplex.com/72CniCMVaHvwpbR Change-Id: I21fb08d36fb08b06691b7eb36c4fa962022c86a6 --- res/drawable/tv_iapp_dialog_background.xml | 21 ++++ res/layout/tv_app_dialog.xml | 71 +++++++++++ res/values/colors.xml | 4 + res/values/strings.xml | 3 + src/com/android/tv/MainActivity.java | 17 ++- .../tv/dialog/InteractiveAppDialogFragment.java | 136 +++++++++++++++++++++ src/com/android/tv/interactive/IAppManager.java | 47 +++++-- src/com/android/tv/ui/TvOverlayManager.java | 2 + 8 files changed, 292 insertions(+), 9 deletions(-) create mode 100755 res/drawable/tv_iapp_dialog_background.xml create mode 100755 res/layout/tv_app_dialog.xml create mode 100755 src/com/android/tv/dialog/InteractiveAppDialogFragment.java diff --git a/res/drawable/tv_iapp_dialog_background.xml b/res/drawable/tv_iapp_dialog_background.xml new file mode 100755 index 00000000..3f6f8e6c --- /dev/null +++ b/res/drawable/tv_iapp_dialog_background.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/res/layout/tv_app_dialog.xml b/res/layout/tv_app_dialog.xml new file mode 100755 index 00000000..e12e0bf7 --- /dev/null +++ b/res/layout/tv_app_dialog.xml @@ -0,0 +1,71 @@ + + + + + + +