aboutsummaryrefslogtreecommitdiff
path: root/tests/common/src
diff options
context:
space:
mode:
Diffstat (limited to 'tests/common/src')
-rw-r--r--tests/common/src/com/android/tv/input/TunerHelper.java23
-rw-r--r--tests/common/src/com/android/tv/testing/ChannelNumberSubject.java66
-rw-r--r--tests/common/src/com/android/tv/testing/ComparableTester.java48
-rw-r--r--tests/common/src/com/android/tv/testing/ComparatorTester.java55
-rw-r--r--tests/common/src/com/android/tv/testing/Constants.java42
-rw-r--r--tests/common/src/com/android/tv/testing/DbTestingUtils.java40
-rw-r--r--tests/common/src/com/android/tv/testing/EpgTestData.java198
-rw-r--r--tests/common/src/com/android/tv/testing/FakeClock.java25
-rw-r--r--tests/common/src/com/android/tv/testing/FakeEpgFetcher.java57
-rw-r--r--tests/common/src/com/android/tv/testing/FakeEpgReader.java164
-rw-r--r--tests/common/src/com/android/tv/testing/FakeRemoteConfig.java55
-rw-r--r--tests/common/src/com/android/tv/testing/FakeTvInputManager.java117
-rw-r--r--tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java32
-rw-r--r--tests/common/src/com/android/tv/testing/FakeTvProvider.java2605
-rw-r--r--tests/common/src/com/android/tv/testing/SingletonProvider.java37
-rw-r--r--tests/common/src/com/android/tv/testing/TestSingletonApp.java247
-rw-r--r--tests/common/src/com/android/tv/testing/activities/BaseMainActivityTestCase.java141
-rw-r--r--tests/common/src/com/android/tv/testing/constants/ConfigConstants.java28
-rw-r--r--tests/common/src/com/android/tv/testing/constants/Constants.java47
-rw-r--r--tests/common/src/com/android/tv/testing/constants/TvContentRatingConstants.java (renamed from tests/common/src/com/android/tv/testing/TvContentRatingConstants.java)18
-rw-r--r--tests/common/src/com/android/tv/testing/data/ChannelInfo.java (renamed from tests/common/src/com/android/tv/testing/ChannelInfo.java)159
-rw-r--r--tests/common/src/com/android/tv/testing/data/ChannelUtils.java (renamed from tests/common/src/com/android/tv/testing/ChannelUtils.java)32
-rw-r--r--tests/common/src/com/android/tv/testing/data/ProgramInfo.java (renamed from tests/common/src/com/android/tv/testing/ProgramInfo.java)167
-rw-r--r--tests/common/src/com/android/tv/testing/data/ProgramUtils.java (renamed from tests/common/src/com/android/tv/testing/ProgramUtils.java)68
-rw-r--r--tests/common/src/com/android/tv/testing/dvr/DvrDataManagerInMemoryImpl.java327
-rw-r--r--tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java36
-rw-r--r--tests/common/src/com/android/tv/testing/robo/ContentProviders.java40
-rw-r--r--tests/common/src/com/android/tv/testing/robo/RobotTestAppHelper.java36
-rw-r--r--tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java89
-rw-r--r--tests/common/src/com/android/tv/testing/testdata/TestData.java86
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/ChannelState.java35
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java31
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java13
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java14
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java13
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java4
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/ByResource.java7
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/Constants.java6
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java9
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java44
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java89
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java11
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java73
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java73
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java29
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java7
-rw-r--r--tests/common/src/com/android/tv/testing/utils/TestUtils.java193
-rw-r--r--tests/common/src/com/android/tv/testing/utils/Utils.java (renamed from tests/common/src/com/android/tv/testing/Utils.java)39
48 files changed, 5189 insertions, 586 deletions
diff --git a/tests/common/src/com/android/tv/input/TunerHelper.java b/tests/common/src/com/android/tv/input/TunerHelper.java
index 126d5027..08c4b041 100644
--- a/tests/common/src/com/android/tv/input/TunerHelper.java
+++ b/tests/common/src/com/android/tv/input/TunerHelper.java
@@ -19,14 +19,11 @@ package com.android.tv.input;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Log;
-
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
-/**
- * A class to manage fake tuners for the tune and the recording.
- */
+/** A class to manage fake tuners for the tune and the recording. */
public class TunerHelper {
private static final String TAG = "TunerHelper";
private static final boolean DEBUG = false;
@@ -38,9 +35,7 @@ public class TunerHelper {
mTunerCount = tunerCount;
}
- /**
- * Checks whether there are available tuners for the recording.
- */
+ /** Checks whether there are available tuners for the recording. */
public boolean tunerAvailableForRecording() {
if (mTuners.size() < mTunerCount) {
return true;
@@ -54,8 +49,8 @@ public class TunerHelper {
}
/**
- * Checks whether there is available tuner.
- * If there's available tuner, it is assigned to the channel.
+ * Checks whether there is available tuner. If there's available tuner, it is assigned to the
+ * channel.
*/
public boolean tune(@Nullable Uri channelUri, boolean forRecording) {
if (channelUri == null) {
@@ -82,9 +77,7 @@ public class TunerHelper {
return false;
}
- /**
- * Releases the tuner which was being used for the tune.
- */
+ /** Releases the tuner which was being used for the tune. */
public void stopTune(@Nullable Uri channelUri) {
if (channelUri == null) {
return;
@@ -110,9 +103,7 @@ public class TunerHelper {
}
}
- /**
- * Releases the tuner which was being used for the recording.
- */
+ /** Releases the tuner which was being used for the recording. */
public void stopRecording(@Nullable Uri channelUri) {
if (channelUri == null) {
return;
@@ -146,7 +137,7 @@ public class TunerHelper {
public boolean tuning;
public boolean recording;
- public Tuner (Uri channelUri, boolean forRecording) {
+ public Tuner(Uri channelUri, boolean forRecording) {
this.channelUri = channelUri;
this.tuning = !forRecording;
this.recording = forRecording;
diff --git a/tests/common/src/com/android/tv/testing/ChannelNumberSubject.java b/tests/common/src/com/android/tv/testing/ChannelNumberSubject.java
new file mode 100644
index 00000000..ba4662ee
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/ChannelNumberSubject.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import android.support.annotation.Nullable;
+import com.android.tv.data.ChannelNumber;
+import com.google.common.truth.ComparableSubject;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+/** Propositions for {@link ChannelNumber} subjects. */
+public final class ChannelNumberSubject
+ extends ComparableSubject<ChannelNumberSubject, ChannelNumber> {
+ private static final Subject.Factory<ChannelNumberSubject, ChannelNumber> FACTORY =
+ ChannelNumberSubject::new;
+
+ public static Subject.Factory<ChannelNumberSubject, ChannelNumber> channelNumbers() {
+ return FACTORY;
+ }
+
+ public static ChannelNumberSubject assertThat(@Nullable ChannelNumber actual) {
+ return Truth.assertAbout(channelNumbers()).that(actual);
+ }
+
+ public ChannelNumberSubject(FailureMetadata failureMetadata, @Nullable ChannelNumber subject) {
+ super(failureMetadata, subject);
+ }
+
+ public void displaysAs(int major) {
+ if (!getSubject().majorNumber.equals(Integer.toString(major))
+ || getSubject().hasDelimiter) {
+ fail("displaysAs", major);
+ }
+ }
+
+ public void displaysAs(int major, int minor) {
+ if (!getSubject().majorNumber.equals(Integer.toString(major))
+ || !getSubject().minorNumber.equals(Integer.toString(minor))
+ || !getSubject().hasDelimiter) {
+ fail("displaysAs", major + "-" + minor);
+ }
+ }
+
+ public void isEmpty() {
+ if (!getSubject().majorNumber.isEmpty()
+ || !getSubject().minorNumber.isEmpty()
+ || getSubject().hasDelimiter) {
+ fail("isEmpty");
+ }
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/ComparableTester.java b/tests/common/src/com/android/tv/testing/ComparableTester.java
index fe6e72f5..4328deb9 100644
--- a/tests/common/src/com/android/tv/testing/ComparableTester.java
+++ b/tests/common/src/com/android/tv/testing/ComparableTester.java
@@ -16,22 +16,19 @@
package com.android.tv.testing;
-import junit.framework.Assert;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import junit.framework.Assert;
/**
* Tester for {@link java.lang.Comparable}s.
*
- * <p>
- * To use, create a new {@link ComparableTester} and add comparable groups
- * where each group contains objects that are
- * {@link java.util.Comparator#compare(Object, Object)} == 0 to each other.
- * Groups are added in order asserting that all earlier groups have compare < 0
- * for all later groups.
+ * <p>To use, create a new {@link ComparableTester} and add comparable groups where each group
+ * contains objects that are {@link java.util.Comparator#compare(Object, Object)} == 0 to each
+ * other. Groups are added in order asserting that all earlier groups have compare < 0 for all later
+ * groups.
*
* <pre>{@code
* new ComparableTester<String>()
@@ -39,8 +36,7 @@ import java.util.List;
* .addEquivalentGroup("World", "wORLD")
* .addEquivalentGroup("ZEBRA")
* .test();
- * }
- * </pre>
+ * }</pre>
*
* @param <T> the type of objects to compare.
*/
@@ -74,8 +70,8 @@ public class ComparableTester<T extends Comparable<T>> {
assertMore(more, less, moreGroup, lessGroup);
}
- private void assertLess(int left, int right, Collection<T> leftGroup,
- Collection<T> rightGroup) {
+ private void assertLess(
+ int left, int right, Collection<T> leftGroup, Collection<T> rightGroup) {
int leftSub = 0;
for (T leftItem : leftGroup) {
int rightSub = 0;
@@ -83,14 +79,22 @@ public class ComparableTester<T extends Comparable<T>> {
for (T rightItem : rightGroup) {
String rightName = "Item[" + right + "," + (rightSub++) + "]";
Assert.assertEquals(
- leftName + " " + leftItem + " compareTo " + rightName + " " + rightItem
- + " is <0", true, leftItem.compareTo(rightItem) < 0);
+ leftName
+ + " "
+ + leftItem
+ + " compareTo "
+ + rightName
+ + " "
+ + rightItem
+ + " is <0",
+ true,
+ leftItem.compareTo(rightItem) < 0);
}
}
}
- private void assertMore(int left, int right, Collection<T> leftGroup,
- Collection<T> rightGroup) {
+ private void assertMore(
+ int left, int right, Collection<T> leftGroup, Collection<T> rightGroup) {
int leftSub = 0;
for (T leftItem : leftGroup) {
int rightSub = 0;
@@ -98,8 +102,16 @@ public class ComparableTester<T extends Comparable<T>> {
for (T rightItem : rightGroup) {
String rightName = "Item[" + right + "," + (rightSub++) + "]";
Assert.assertEquals(
- leftName + " " + leftItem + " compareTo " + rightName + " " + rightItem
- + " is >0", true, leftItem.compareTo(rightItem) > 0);
+ leftName
+ + " "
+ + leftItem
+ + " compareTo "
+ + rightName
+ + " "
+ + rightItem
+ + " is >0",
+ true,
+ leftItem.compareTo(rightItem) > 0);
}
}
}
diff --git a/tests/common/src/com/android/tv/testing/ComparatorTester.java b/tests/common/src/com/android/tv/testing/ComparatorTester.java
index 3774532f..6ebd8b4e 100644
--- a/tests/common/src/com/android/tv/testing/ComparatorTester.java
+++ b/tests/common/src/com/android/tv/testing/ComparatorTester.java
@@ -27,12 +27,9 @@ import java.util.List;
/**
* Tester for {@link Comparator} relationships between groups of T.
*
- * <p>
- * To use, create a new {@link ComparatorTester} and add comparable groups
- * where each group contains objects that are
- * {@link Comparator#compare(Object, Object)} == 0 to each other.
- * Groups are added in order asserting that all earlier groups have compare < 0
- * for all later groups.
+ * <p>To use, create a new {@link ComparatorTester} and add comparable groups where each group
+ * contains objects that are {@link Comparator#compare(Object, Object)} == 0 to each other. Groups
+ * are added in order asserting that all earlier groups have compare < 0 for all later groups.
*
* <pre>{@code
* ComparatorTester
@@ -41,8 +38,7 @@ import java.util.List;
* .addComparableGroup("World", "wORLD")
* .addComparableGroup("ZEBRA")
* .test();
- * }
- * </pre>
+ * }</pre>
*
* @param <T> the type of objects to compare.
*/
@@ -52,7 +48,6 @@ public class ComparatorTester<T> {
private final Comparator<T> comparator;
-
public static <T> ComparatorTester<T> withoutEqualsTest(Comparator<T> comparator) {
return new ComparatorTester<>(comparator);
}
@@ -80,7 +75,7 @@ public class ComparatorTester<T> {
assertOrder(i, j, currentGroup, rhs);
}
}
- //TODO: also test equals
+ // TODO: also test equals
}
private void assertOrder(int less, int more, List<T> lessGroup, List<T> moreGroup) {
@@ -88,30 +83,48 @@ public class ComparatorTester<T> {
assertMore(more, less, moreGroup, lessGroup);
}
- private void assertLess(int left, int right, Collection<T> leftGroup,
- Collection<T> rightGroup) {
+ private void assertLess(
+ int left, int right, Collection<T> leftGroup, Collection<T> rightGroup) {
int leftSub = 0;
for (T leftItem : leftGroup) {
int rightSub = 0;
for (T rightItem : rightGroup) {
String leftName = "Item[" + left + "," + (leftSub++) + "]";
String rName = "Item[" + right + "," + (rightSub++) + "]";
- assertEquals(leftName + " " + leftItem + " compareTo " + rName + " " + rightItem
- + " is <0", true, comparator.compare(leftItem, rightItem) < 0);
+ assertEquals(
+ leftName
+ + " "
+ + leftItem
+ + " compareTo "
+ + rName
+ + " "
+ + rightItem
+ + " is <0",
+ true,
+ comparator.compare(leftItem, rightItem) < 0);
}
}
}
- private void assertMore(int left, int right, Collection<T> leftGroup,
- Collection<T> rightGroup) {
+ private void assertMore(
+ int left, int right, Collection<T> leftGroup, Collection<T> rightGroup) {
int leftSub = 0;
for (T leftItem : leftGroup) {
int rightSub = 0;
for (T rightItem : rightGroup) {
String leftName = "Item[" + left + "," + (leftSub++) + "]";
String rName = "Item[" + right + "," + (rightSub++) + "]";
- assertEquals(leftName + " " + leftItem + " compareTo " + rName + " " + rightItem
- + " is >0", true, comparator.compare(leftItem, rightItem) > 0);
+ assertEquals(
+ leftName
+ + " "
+ + leftItem
+ + " compareTo "
+ + rName
+ + " "
+ + rightItem
+ + " is >0",
+ true,
+ comparator.compare(leftItem, rightItem) > 0);
}
}
}
@@ -120,9 +133,11 @@ public class ComparatorTester<T> {
// Test everything against everything in both directions, including against itself.
for (T leftItem : group) {
for (T rightItem : group) {
- assertEquals(leftItem + " compareTo " + rightItem, 0,
+ assertEquals(
+ leftItem + " compareTo " + rightItem,
+ 0,
comparator.compare(leftItem, rightItem));
}
}
}
-} \ No newline at end of file
+}
diff --git a/tests/common/src/com/android/tv/testing/Constants.java b/tests/common/src/com/android/tv/testing/Constants.java
deleted file mode 100644
index 4c9cb5fb..00000000
--- a/tests/common/src/com/android/tv/testing/Constants.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 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.testing;
-
-import android.media.tv.TvTrackInfo;
-
-/**
- * Constants for testing.
- */
-public final class Constants {
- public static final int FUNC_TEST_CHANNEL_COUNT = 100;
- public static final int UNIT_TEST_CHANNEL_COUNT = 4;
- public static final int JANK_TEST_CHANNEL_COUNT = 500; // TODO: increase to 1000 see b/23526997
-
- public static final TvTrackInfo EN_STEREO_AUDIO_TRACK = new TvTrackInfo.Builder(
- TvTrackInfo.TYPE_AUDIO, "English Stereo Audio").setLanguage("en")
- .setAudioChannelCount(2).build();
- public static final TvTrackInfo GENERIC_AUDIO_TRACK = new TvTrackInfo.Builder(
- TvTrackInfo.TYPE_AUDIO, "Generic Audio").build();
-
- public static final TvTrackInfo FHD1080P50_VIDEO_TRACK = new TvTrackInfo.Builder(
- TvTrackInfo.TYPE_VIDEO, "FHD Video").setVideoHeight(1080).setVideoWidth(1920)
- .setVideoFrameRate(50).build();
- public static final TvTrackInfo SVGA_VIDEO_TRACK = new TvTrackInfo.Builder(
- TvTrackInfo.TYPE_VIDEO, "SVGA Video").setVideoHeight(600).setVideoWidth(800).build();
-
- private Constants() {
- }
-}
diff --git a/tests/common/src/com/android/tv/testing/DbTestingUtils.java b/tests/common/src/com/android/tv/testing/DbTestingUtils.java
new file mode 100644
index 00000000..53e26ca7
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/DbTestingUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import android.database.Cursor;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Static utilities for testing using databases. */
+public final class DbTestingUtils {
+
+ public static List<List<String>> toList(Cursor cursor) {
+ ArrayList<List<String>> result = new ArrayList<>();
+ int colCount = cursor.getColumnCount();
+ while (cursor.moveToNext()) {
+ List<String> row = new ArrayList<>(colCount);
+ for (int i = 0; i < colCount; i++) {
+ row.add(cursor.getString(i));
+ }
+ result.add(row);
+ }
+ return result;
+ }
+
+ private DbTestingUtils() {}
+}
diff --git a/tests/common/src/com/android/tv/testing/EpgTestData.java b/tests/common/src/com/android/tv/testing/EpgTestData.java
new file mode 100644
index 00000000..49a92181
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/EpgTestData.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import com.android.tv.data.ChannelImpl;
+import com.android.tv.data.Lineup;
+import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import java.util.concurrent.TimeUnit;
+
+/** EPG data for use in tests. */
+public abstract class EpgTestData {
+
+ public static final android.support.media.tv.Channel CHANNEL_10 =
+ new android.support.media.tv.Channel.Builder()
+ .setDisplayName("Channel TEN")
+ .setDisplayNumber("10")
+ .build();
+ public static final android.support.media.tv.Channel CHANNEL_11 =
+ new android.support.media.tv.Channel.Builder()
+ .setDisplayName("Channel Eleven")
+ .setDisplayNumber("11")
+ .build();
+ public static final android.support.media.tv.Channel CHANNEL_90_2 =
+ new android.support.media.tv.Channel.Builder()
+ .setDisplayName("Channel Ninety dot Two")
+ .setDisplayNumber("90.2")
+ .build();
+
+ public static final Lineup LINEUP_1 =
+ new Lineup(
+ "lineup1",
+ Lineup.LINEUP_SATELLITE,
+ "Lineup one",
+ "Location one",
+ ImmutableList.of("1", "2.2"));
+ public static final Lineup LINEUP_2 =
+ new Lineup(
+ "lineup2",
+ Lineup.LINEUP_SATELLITE,
+ "Lineup two",
+ "Location two",
+ ImmutableList.of("1", "2.3"));
+
+ public static final Lineup LINEUP_90210 =
+ new Lineup(
+ "test90210",
+ Lineup.LINEUP_BROADCAST_DIGITAL,
+ "Test 90210",
+ "Beverly Hills",
+ ImmutableList.of("90.2", "10"));
+
+ // Programs start and end times are set relative to 0.
+ // Then when loaded they are offset by the {@link #getStartTimeMs}.
+ // Start and end time may be negative meaning they happen before "now".
+
+ public static final Program PROGRAM_1 =
+ new Program.Builder()
+ .setTitle("Program 1")
+ .setStartTimeUtcMillis(0)
+ .setEndTimeUtcMillis(TimeUnit.MINUTES.toMillis(30))
+ .build();
+
+ public static final Program PROGRAM_2 =
+ new Program.Builder()
+ .setTitle("Program 2")
+ .setStartTimeUtcMillis(TimeUnit.MINUTES.toMillis(30))
+ .setEndTimeUtcMillis(TimeUnit.MINUTES.toMillis(60))
+ .build();
+
+ public static final EpgTestData DATA_90210 =
+ new EpgTestData() {
+
+ // Thursday, June 1, 2017 4:00:00 PM GMT-07:00
+ private final long testStartTimeMs = 1496358000000L;
+
+ @Override
+ public ListMultimap<String, Lineup> getLineups() {
+ ImmutableListMultimap.Builder<String, Lineup> builder =
+ ImmutableListMultimap.builder();
+ return builder.putAll("90210", LINEUP_1, LINEUP_2, LINEUP_90210).build();
+ }
+
+ @Override
+ public ListMultimap<String, Channel> getLineupChannels() {
+ ImmutableListMultimap.Builder<String, Channel> builder =
+ ImmutableListMultimap.builder();
+ return builder.putAll(
+ LINEUP_90210.getId(), toTvChannels(CHANNEL_90_2, CHANNEL_10))
+ .putAll(LINEUP_1.getId(), toTvChannels(CHANNEL_10, CHANNEL_11))
+ .build();
+ }
+
+ @Override
+ public ListMultimap<String, Program> getEpgPrograms() {
+ ImmutableListMultimap.Builder<String, Program> builder =
+ ImmutableListMultimap.builder();
+ return builder.putAll(
+ CHANNEL_10.getDisplayNumber(),
+ EpgTestData.updateTime(getStartTimeMs(), PROGRAM_1))
+ .putAll(
+ CHANNEL_11.getDisplayNumber(),
+ EpgTestData.updateTime(getStartTimeMs(), PROGRAM_2))
+ .build();
+ }
+
+ @Override
+ public long getStartTimeMs() {
+ return testStartTimeMs;
+ }
+ };
+
+ public abstract ListMultimap<String, Lineup> getLineups();
+
+ public abstract ListMultimap<String, Channel> getLineupChannels();
+
+ public abstract ListMultimap<String, Program> getEpgPrograms();
+
+ /** The starting time for this test data */
+ public abstract long getStartTimeMs();
+
+ /**
+ * Loads test data
+ *
+ * <p>
+ *
+ * <ul>
+ * <li>Sets clock to {@link #getStartTimeMs()} and boot time to 12 hours before that
+ * <li>Loads lineups
+ * <li>Loads lineupChannels
+ * <li>Loads epgPrograms
+ * </ul>
+ */
+ public final void loadData(FakeClock clock, FakeEpgReader epgReader) {
+ clock.setBootTimeMillis(getStartTimeMs() + TimeUnit.HOURS.toMillis(-12));
+ clock.setCurrentTimeMillis(getStartTimeMs());
+ epgReader.zip2lineups.putAll(getLineups());
+ epgReader.lineup2Channels.putAll(getLineupChannels());
+ epgReader.epgChannelId2Programs.putAll(getEpgPrograms());
+ }
+
+ public final void loadData(TestSingletonApp testSingletonApp) {
+ loadData(testSingletonApp.fakeClock, testSingletonApp.epgReader);
+ }
+
+ private static Iterable<Channel> toTvChannels(android.support.media.tv.Channel... channels) {
+ return Iterables.transform(
+ ImmutableList.copyOf(channels),
+ new Function<android.support.media.tv.Channel, Channel>() {
+ @Override
+ public Channel apply(android.support.media.tv.Channel original) {
+ return toTvChannel(original);
+ }
+ });
+ }
+
+ public static Channel toTvChannel(android.support.media.tv.Channel original) {
+ return new ChannelImpl.Builder()
+ .setDisplayName(original.getDisplayName())
+ .setDisplayNumber(original.getDisplayNumber())
+ // TODO implement the reset
+ .build();
+ }
+
+ /** Add time to the startTime and stopTime of each program */
+ private static Iterable<Program> updateTime(long time, Program... programs) {
+ return Iterables.transform(
+ ImmutableList.copyOf(programs),
+ new Function<Program, Program>() {
+ @Override
+ public Program apply(Program p) {
+ return new Program.Builder(p)
+ .setStartTimeUtcMillis(p.getStartTimeUtcMillis() + time)
+ .setEndTimeUtcMillis(p.getEndTimeUtcMillis() + time)
+ .build();
+ }
+ });
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/FakeClock.java b/tests/common/src/com/android/tv/testing/FakeClock.java
index d88e53a8..f5941939 100644
--- a/tests/common/src/com/android/tv/testing/FakeClock.java
+++ b/tests/common/src/com/android/tv/testing/FakeClock.java
@@ -16,8 +16,7 @@
package com.android.tv.testing;
-import com.android.tv.util.Clock;
-
+import com.android.tv.common.util.Clock;
import java.util.concurrent.TimeUnit;
/**
@@ -27,21 +26,20 @@ import java.util.concurrent.TimeUnit;
* {@link #sleep(long)} is called.
*/
public class FakeClock implements Clock {
- /**
- * Creates a fake clock with the time set to now and the boot time set to now - 100,000.
- */
+ /** Creates a fake clock with the time set to now and the boot time set to now - 100,000. */
public static FakeClock createWithCurrentTime() {
long now = System.currentTimeMillis();
return new FakeClock(now, now - 100_000);
}
- /**
- * Creates a fake clock with the time set to zero.
- */
+ /** Creates a fake clock with the time set to zero. */
public static FakeClock createWithTimeOne() {
return new FakeClock(1L, 0L);
}
-
+ /** Creates a fake clock with the time set to {@code time}. */
+ public static FakeClock createWithTime(long time) {
+ return new FakeClock(time, 0L);
+ }
private long mCurrentTimeMillis;
@@ -95,9 +93,12 @@ public class FakeClock implements Clock {
return mCurrentTimeMillis - mBootTimeMillis;
}
- /**
- * Sleep does not block it just updates the current time.
- */
+ @Override
+ public long uptimeMillis() {
+ return elapsedRealtime();
+ }
+
+ /** Sleep does not block it just updates the current time. */
@Override
public void sleep(long ms) {
// TODO: implement blocking if needed.
diff --git a/tests/common/src/com/android/tv/testing/FakeEpgFetcher.java b/tests/common/src/com/android/tv/testing/FakeEpgFetcher.java
new file mode 100644
index 00000000..d1018a5c
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/FakeEpgFetcher.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import com.android.tv.data.epg.EpgFetcher;
+
+/** Fake {@link EpgFetcher} for testing. */
+public class FakeEpgFetcher implements EpgFetcher {
+ public boolean fetchStarted = false;
+
+ @Override
+ public void startRoutineService() {}
+
+ @Override
+ public void fetchImmediatelyIfNeeded() {}
+
+ @Override
+ public void fetchImmediately() {
+ fetchStarted = true;
+ }
+
+ @Override
+ public void onChannelScanStarted() {}
+
+ @Override
+ public void onChannelScanFinished() {}
+
+ @Override
+ public boolean executeFetchTaskIfPossible(JobService jobService, JobParameters params) {
+ return false;
+ }
+
+ @Override
+ public void stopFetchingJob() {
+ fetchStarted = false;
+ }
+
+ public void reset() {
+ fetchStarted = false;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/FakeEpgReader.java b/tests/common/src/com/android/tv/testing/FakeEpgReader.java
new file mode 100644
index 00000000..710ada55
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/FakeEpgReader.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Range;
+import com.android.tv.data.ChannelImpl;
+import com.android.tv.data.ChannelNumber;
+import com.android.tv.data.Lineup;
+import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
+import com.android.tv.data.epg.EpgReader;
+import com.android.tv.dvr.data.SeriesInfo;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Fake {@link EpgReader} for testing. */
+public final class FakeEpgReader implements EpgReader {
+ public final ListMultimap<String, Lineup> zip2lineups = LinkedListMultimap.create(2);
+ public final ListMultimap<String, Channel> lineup2Channels = LinkedListMultimap.create(2);
+ public final ListMultimap<String, Program> epgChannelId2Programs = LinkedListMultimap.create(2);
+ public final FakeClock fakeClock;
+
+ public FakeEpgReader(FakeClock fakeClock) {
+ this.fakeClock = fakeClock;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public long getEpgTimestamp() {
+ return fakeClock.currentTimeMillis();
+ }
+
+ @Override
+ public void setRegionCode(String regionCode) {}
+
+ @Override
+ public List<Lineup> getLineups(@NonNull String postalCode) {
+ return zip2lineups.get(postalCode);
+ }
+
+ @Override
+ public List<String> getChannelNumbers(@NonNull String lineupId) {
+ return null;
+ }
+
+ @Override
+ public Set<EpgChannel> getChannels(Set<Channel> inputChannels, @NonNull String lineupId) {
+ Set<EpgChannel> result = new HashSet<>();
+ List<Channel> lineupChannels = lineup2Channels.get(lineupId);
+ for (Channel channel : lineupChannels) {
+ Channel match =
+ Iterables.find(
+ inputChannels,
+ new Predicate<Channel>() {
+ @Override
+ public boolean apply(@Nullable Channel inputChannel) {
+ return ChannelNumber.equivalent(
+ inputChannel.getDisplayNumber(),
+ channel.getDisplayNumber());
+ }
+ },
+ null);
+ if (match != null) {
+ ChannelImpl updatedChannel = new ChannelImpl.Builder(match).build();
+ updatedChannel.setLogoUri(channel.getLogoUri());
+ result.add(EpgChannel.createEpgChannel(updatedChannel, channel.getDisplayNumber()));
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void preloadChannels(@NonNull String lineupId) {}
+
+ @Override
+ public void clearCachedChannels(@NonNull String lineupId) {}
+
+ @Override
+ public List<Program> getPrograms(EpgChannel epgChannel) {
+ return ImmutableList.copyOf(
+ Iterables.transform(
+ epgChannelId2Programs.get(epgChannel.getEpgChannelId()),
+ updateWith(epgChannel)));
+ }
+
+ @Override
+ public Map<EpgChannel, Collection<Program>> getPrograms(
+ @NonNull Set<EpgChannel> epgChannels, long duration) {
+ Range<Long> validRange =
+ Range.create(
+ fakeClock.currentTimeMillis(), fakeClock.currentTimeMillis() + duration);
+ ImmutableMap.Builder<EpgChannel, Collection<Program>> mapBuilder = ImmutableMap.builder();
+ for (EpgChannel epgChannel : epgChannels) {
+ Iterable<Program> programs = getPrograms(epgChannel);
+
+ mapBuilder.put(
+ epgChannel,
+ ImmutableList.copyOf(Iterables.filter(programs, isProgramDuring(validRange))));
+ }
+ return mapBuilder.build();
+ }
+
+ protected Function<Program, Program> updateWith(final EpgChannel channel) {
+ return new Function<Program, Program>() {
+ @Nullable
+ @Override
+ public Program apply(@Nullable Program program) {
+ return new Program.Builder(program)
+ .setChannelId(channel.getChannel().getId())
+ .setPackageName(channel.getChannel().getPackageName())
+ .build();
+ }
+ };
+ }
+
+ /**
+ * True if the start time or the end time is {@link Range#contains contained (inclusive)} in the
+ * range
+ */
+ protected Predicate<Program> isProgramDuring(final Range<Long> validRange) {
+ return new Predicate<Program>() {
+ @Override
+ public boolean apply(@Nullable Program program) {
+ return validRange.contains(program.getStartTimeUtcMillis())
+ || validRange.contains(program.getEndTimeUtcMillis());
+ }
+ };
+ }
+
+ @Override
+ public SeriesInfo getSeriesInfo(@NonNull String seriesId) {
+ return null;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/FakeRemoteConfig.java b/tests/common/src/com/android/tv/testing/FakeRemoteConfig.java
new file mode 100644
index 00000000..89e6a0a2
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/FakeRemoteConfig.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import android.text.TextUtils;
+import com.android.tv.common.config.api.RemoteConfig;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Fake {@link RemoteConfig} suitable for testing. */
+public class FakeRemoteConfig implements RemoteConfig {
+ public final Map<String, String> values = new HashMap();
+
+ @Override
+ public void fetch(OnRemoteConfigUpdatedListener listener) {}
+
+ @Override
+ public String getString(String key) {
+ return values.get(key);
+ }
+
+ @Override
+ public boolean getBoolean(String key) {
+ String value = values.get(key);
+ return TextUtils.isEmpty(value) ? false : Boolean.valueOf(key);
+ }
+
+ @Override
+ public long getLong(String key) {
+ return getLong(key, 0);
+ }
+
+ @Override
+ public long getLong(String key, long defaultValue) {
+ if (values.containsKey(key)) {
+ String value = values.get(key);
+ return TextUtils.isEmpty(value) ? defaultValue : Long.valueOf(value);
+ }
+ return defaultValue;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/FakeTvInputManager.java b/tests/common/src/com/android/tv/testing/FakeTvInputManager.java
new file mode 100644
index 00000000..397b4052
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/FakeTvInputManager.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import android.media.tv.TvContentRatingSystemInfo;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
+import android.os.Handler;
+import com.android.tv.util.TvInputManagerHelper;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Fake implementation for testing. */
+public class FakeTvInputManager implements TvInputManagerHelper.TvInputManagerInterface {
+
+ private final Map<String, Integer> mInputStateMap = new HashMap<>();
+ private final Map<String, TvInputInfo> mInputMap = new HashMap<>();
+ private final Map<TvInputManager.TvInputCallback, Handler> mCallbacks = new HashMap<>();
+
+ public void add(TvInputInfo inputInfo, int state) {
+ final String inputId = inputInfo.getId();
+ if (mInputStateMap.containsKey(inputId)) {
+ throw new IllegalArgumentException("inputId " + inputId);
+ }
+ mInputMap.put(inputId, inputInfo);
+ mInputStateMap.put(inputId, state);
+ for (final Map.Entry<TvInputManager.TvInputCallback, Handler> e : mCallbacks.entrySet()) {
+ e.getValue()
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ e.getKey().onInputAdded(inputId);
+ }
+ });
+ }
+ }
+
+ public void setInputState(final String inputId, final int state) {
+ if (!mInputStateMap.containsKey(inputId)) {
+ throw new IllegalArgumentException("inputId " + inputId);
+ }
+ mInputStateMap.put(inputId, state);
+ for (final Map.Entry<TvInputManager.TvInputCallback, Handler> e : mCallbacks.entrySet()) {
+ e.getValue()
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ e.getKey().onInputStateChanged(inputId, state);
+ }
+ });
+ }
+ }
+
+ public void remove(final String inputId) {
+ mInputMap.remove(inputId);
+ mInputStateMap.remove(inputId);
+ for (final Map.Entry<TvInputManager.TvInputCallback, Handler> e : mCallbacks.entrySet()) {
+ e.getValue()
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ e.getKey().onInputRemoved(inputId);
+ }
+ });
+ }
+ }
+
+ @Override
+ public TvInputInfo getTvInputInfo(String inputId) {
+ return mInputMap.get(inputId);
+ }
+
+ @Override
+ public Integer getInputState(String inputId) {
+ return mInputStateMap.get(inputId);
+ }
+
+ @Override
+ public void registerCallback(TvInputManager.TvInputCallback internalCallback, Handler handler) {
+ mCallbacks.put(internalCallback, handler);
+ }
+
+ @Override
+ public void unregisterCallback(TvInputManager.TvInputCallback internalCallback) {
+ mCallbacks.remove(internalCallback);
+ }
+
+ @Override
+ public List<TvInputInfo> getTvInputList() {
+ return new ArrayList(mInputMap.values());
+ }
+
+ @Override
+ public List<TvContentRatingSystemInfo> getTvContentRatingSystemList() {
+ // TODO implement
+ return new ArrayList<>();
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java b/tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java
new file mode 100644
index 00000000..85bdcf04
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import android.content.Context;
+import com.android.tv.util.TvInputManagerHelper;
+
+/** Fake TvInputManagerHelper. */
+public class FakeTvInputManagerHelper extends TvInputManagerHelper {
+
+ public FakeTvInputManagerHelper(Context context) {
+ super(context, new FakeTvInputManager());
+ }
+
+ public FakeTvInputManager getFakeTvInputManager() {
+ return (FakeTvInputManager) mTvInputManager;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/FakeTvProvider.java b/tests/common/src/com/android/tv/testing/FakeTvProvider.java
new file mode 100644
index 00000000..24c26f39
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/FakeTvProvider.java
@@ -0,0 +1,2605 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import android.annotation.SuppressLint;
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.content.SharedPreferences;
+import android.content.UriMatcher;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.preference.PreferenceManager;
+import android.provider.BaseColumns;
+import android.support.annotation.VisibleForTesting;
+import android.support.media.tv.TvContractCompat;
+import android.support.media.tv.TvContractCompat.BaseTvColumns;
+import android.support.media.tv.TvContractCompat.Channels;
+import android.support.media.tv.TvContractCompat.PreviewPrograms;
+import android.support.media.tv.TvContractCompat.Programs;
+import android.support.media.tv.TvContractCompat.Programs.Genres;
+import android.support.media.tv.TvContractCompat.RecordedPrograms;
+import android.support.media.tv.TvContractCompat.WatchNextPrograms;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.tv.util.SqlParams;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Fake TV content provider suitable for unit tests. The contract between this provider and
+ * applications is defined in {@link TvContractCompat}.
+ */
+// TODO(b/62143348): remove when error prone check fixed
+@SuppressWarnings({"AndroidApiChecker", "TryWithResources"})
+public class FakeTvProvider extends ContentProvider {
+ // TODO either make this a shadow or move it to the support library
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvProvider";
+
+ static final int DATABASE_VERSION = 34;
+ static final String SHARED_PREF_BLOCKED_PACKAGES_KEY = "blocked_packages";
+ static final String CHANNELS_TABLE = "channels";
+ static final String PROGRAMS_TABLE = "programs";
+ static final String RECORDED_PROGRAMS_TABLE = "recorded_programs";
+ static final String PREVIEW_PROGRAMS_TABLE = "preview_programs";
+ static final String WATCH_NEXT_PROGRAMS_TABLE = "watch_next_programs";
+ static final String WATCHED_PROGRAMS_TABLE = "watched_programs";
+ static final String PROGRAMS_TABLE_PACKAGE_NAME_INDEX = "programs_package_name_index";
+ static final String PROGRAMS_TABLE_CHANNEL_ID_INDEX = "programs_channel_id_index";
+ static final String PROGRAMS_TABLE_START_TIME_INDEX = "programs_start_time_index";
+ static final String PROGRAMS_TABLE_END_TIME_INDEX = "programs_end_time_index";
+ static final String WATCHED_PROGRAMS_TABLE_CHANNEL_ID_INDEX =
+ "watched_programs_channel_id_index";
+ // The internal column in the watched programs table to indicate whether the current log entry
+ // is consolidated or not. Unconsolidated entries may have columns with missing data.
+ static final String WATCHED_PROGRAMS_COLUMN_CONSOLIDATED = "consolidated";
+ static final String CHANNELS_COLUMN_LOGO = "logo";
+ private static final String DATABASE_NAME = "tv.db";
+ private static final String DELETED_CHANNELS_TABLE = "deleted_channels"; // Deprecated
+ private static final String DEFAULT_PROGRAMS_SORT_ORDER =
+ Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
+ private static final String DEFAULT_WATCHED_PROGRAMS_SORT_ORDER =
+ WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + " DESC";
+ private static final String CHANNELS_TABLE_INNER_JOIN_PROGRAMS_TABLE =
+ CHANNELS_TABLE
+ + " INNER JOIN "
+ + PROGRAMS_TABLE
+ + " ON ("
+ + CHANNELS_TABLE
+ + "."
+ + Channels._ID
+ + "="
+ + PROGRAMS_TABLE
+ + "."
+ + Programs.COLUMN_CHANNEL_ID
+ + ")";
+
+ // Operation names for createSqlParams().
+ private static final String OP_QUERY = "query";
+ private static final String OP_UPDATE = "update";
+ private static final String OP_DELETE = "delete";
+
+ private static final UriMatcher sUriMatcher;
+ private static final int MATCH_CHANNEL = 1;
+ private static final int MATCH_CHANNEL_ID = 2;
+ private static final int MATCH_CHANNEL_ID_LOGO = 3;
+ private static final int MATCH_PASSTHROUGH_ID = 4;
+ private static final int MATCH_PROGRAM = 5;
+ private static final int MATCH_PROGRAM_ID = 6;
+ private static final int MATCH_WATCHED_PROGRAM = 7;
+ private static final int MATCH_WATCHED_PROGRAM_ID = 8;
+ private static final int MATCH_RECORDED_PROGRAM = 9;
+ private static final int MATCH_RECORDED_PROGRAM_ID = 10;
+ private static final int MATCH_PREVIEW_PROGRAM = 11;
+ private static final int MATCH_PREVIEW_PROGRAM_ID = 12;
+ private static final int MATCH_WATCH_NEXT_PROGRAM = 13;
+ private static final int MATCH_WATCH_NEXT_PROGRAM_ID = 14;
+
+ private static final int MAX_LOGO_IMAGE_SIZE = 256;
+
+ private static final String EMPTY_STRING = "";
+
+ private static final Map<String, String> sChannelProjectionMap;
+ private static final Map<String, String> sProgramProjectionMap;
+ private static final Map<String, String> sWatchedProgramProjectionMap;
+ private static final Map<String, String> sRecordedProgramProjectionMap;
+ private static final Map<String, String> sPreviewProgramProjectionMap;
+ private static final Map<String, String> sWatchNextProgramProjectionMap;
+
+ // TvContract hidden
+ private static final String PARAM_PACKAGE = "package";
+ private static final String PARAM_PREVIEW = "preview";
+
+ private static boolean sInitialized;
+
+ static {
+ sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "channel", MATCH_CHANNEL);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "channel/#", MATCH_CHANNEL_ID);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "channel/#/logo", MATCH_CHANNEL_ID_LOGO);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "passthrough/*", MATCH_PASSTHROUGH_ID);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "program", MATCH_PROGRAM);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "program/#", MATCH_PROGRAM_ID);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "watched_program", MATCH_WATCHED_PROGRAM);
+ sUriMatcher.addURI(
+ TvContractCompat.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "recorded_program", MATCH_RECORDED_PROGRAM);
+ sUriMatcher.addURI(
+ TvContractCompat.AUTHORITY, "recorded_program/#", MATCH_RECORDED_PROGRAM_ID);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "preview_program", MATCH_PREVIEW_PROGRAM);
+ sUriMatcher.addURI(
+ TvContractCompat.AUTHORITY, "preview_program/#", MATCH_PREVIEW_PROGRAM_ID);
+ sUriMatcher.addURI(
+ TvContractCompat.AUTHORITY, "watch_next_program", MATCH_WATCH_NEXT_PROGRAM);
+ sUriMatcher.addURI(
+ TvContractCompat.AUTHORITY, "watch_next_program/#", MATCH_WATCH_NEXT_PROGRAM_ID);
+
+ sChannelProjectionMap = new HashMap<>();
+ sChannelProjectionMap.put(Channels._ID, CHANNELS_TABLE + "." + Channels._ID);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_PACKAGE_NAME, CHANNELS_TABLE + "." + Channels.COLUMN_PACKAGE_NAME);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INPUT_ID, CHANNELS_TABLE + "." + Channels.COLUMN_INPUT_ID);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_TYPE, CHANNELS_TABLE + "." + Channels.COLUMN_TYPE);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_SERVICE_TYPE, CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_TYPE);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_ORIGINAL_NETWORK_ID,
+ CHANNELS_TABLE + "." + Channels.COLUMN_ORIGINAL_NETWORK_ID);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_TRANSPORT_STREAM_ID,
+ CHANNELS_TABLE + "." + Channels.COLUMN_TRANSPORT_STREAM_ID);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_SERVICE_ID, CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_ID);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_DISPLAY_NUMBER,
+ CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NUMBER);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_DISPLAY_NAME, CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NAME);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_NETWORK_AFFILIATION,
+ CHANNELS_TABLE + "." + Channels.COLUMN_NETWORK_AFFILIATION);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_DESCRIPTION, CHANNELS_TABLE + "." + Channels.COLUMN_DESCRIPTION);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_VIDEO_FORMAT, CHANNELS_TABLE + "." + Channels.COLUMN_VIDEO_FORMAT);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_BROWSABLE, CHANNELS_TABLE + "." + Channels.COLUMN_BROWSABLE);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_SEARCHABLE, CHANNELS_TABLE + "." + Channels.COLUMN_SEARCHABLE);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_LOCKED, CHANNELS_TABLE + "." + Channels.COLUMN_LOCKED);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_APP_LINK_ICON_URI,
+ CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_ICON_URI);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_APP_LINK_POSTER_ART_URI,
+ CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_POSTER_ART_URI);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_APP_LINK_TEXT,
+ CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_TEXT);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_APP_LINK_COLOR,
+ CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_COLOR);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_APP_LINK_INTENT_URI,
+ CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_INTENT_URI);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_DATA,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_DATA);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_VERSION_NUMBER,
+ CHANNELS_TABLE + "." + Channels.COLUMN_VERSION_NUMBER);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_TRANSIENT, CHANNELS_TABLE + "." + Channels.COLUMN_TRANSIENT);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_ID,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_ID);
+
+ sProgramProjectionMap = new HashMap<>();
+ sProgramProjectionMap.put(Programs._ID, Programs._ID);
+ sProgramProjectionMap.put(Programs.COLUMN_PACKAGE_NAME, Programs.COLUMN_PACKAGE_NAME);
+ sProgramProjectionMap.put(Programs.COLUMN_CHANNEL_ID, Programs.COLUMN_CHANNEL_ID);
+ sProgramProjectionMap.put(Programs.COLUMN_TITLE, Programs.COLUMN_TITLE);
+ // COLUMN_SEASON_NUMBER is deprecated. Return COLUMN_SEASON_DISPLAY_NUMBER instead.
+ sProgramProjectionMap.put(
+ Programs.COLUMN_SEASON_NUMBER,
+ Programs.COLUMN_SEASON_DISPLAY_NUMBER + " AS " + Programs.COLUMN_SEASON_NUMBER);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_SEASON_DISPLAY_NUMBER, Programs.COLUMN_SEASON_DISPLAY_NUMBER);
+ sProgramProjectionMap.put(Programs.COLUMN_SEASON_TITLE, Programs.COLUMN_SEASON_TITLE);
+ // COLUMN_EPISODE_NUMBER is deprecated. Return COLUMN_EPISODE_DISPLAY_NUMBER instead.
+ sProgramProjectionMap.put(
+ Programs.COLUMN_EPISODE_NUMBER,
+ Programs.COLUMN_EPISODE_DISPLAY_NUMBER + " AS " + Programs.COLUMN_EPISODE_NUMBER);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_EPISODE_DISPLAY_NUMBER, Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
+ sProgramProjectionMap.put(Programs.COLUMN_EPISODE_TITLE, Programs.COLUMN_EPISODE_TITLE);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_START_TIME_UTC_MILLIS, Programs.COLUMN_START_TIME_UTC_MILLIS);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_END_TIME_UTC_MILLIS, Programs.COLUMN_END_TIME_UTC_MILLIS);
+ sProgramProjectionMap.put(Programs.COLUMN_BROADCAST_GENRE, Programs.COLUMN_BROADCAST_GENRE);
+ sProgramProjectionMap.put(Programs.COLUMN_CANONICAL_GENRE, Programs.COLUMN_CANONICAL_GENRE);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_SHORT_DESCRIPTION, Programs.COLUMN_SHORT_DESCRIPTION);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_LONG_DESCRIPTION, Programs.COLUMN_LONG_DESCRIPTION);
+ sProgramProjectionMap.put(Programs.COLUMN_VIDEO_WIDTH, Programs.COLUMN_VIDEO_WIDTH);
+ sProgramProjectionMap.put(Programs.COLUMN_VIDEO_HEIGHT, Programs.COLUMN_VIDEO_HEIGHT);
+ sProgramProjectionMap.put(Programs.COLUMN_AUDIO_LANGUAGE, Programs.COLUMN_AUDIO_LANGUAGE);
+ sProgramProjectionMap.put(Programs.COLUMN_CONTENT_RATING, Programs.COLUMN_CONTENT_RATING);
+ sProgramProjectionMap.put(Programs.COLUMN_POSTER_ART_URI, Programs.COLUMN_POSTER_ART_URI);
+ sProgramProjectionMap.put(Programs.COLUMN_THUMBNAIL_URI, Programs.COLUMN_THUMBNAIL_URI);
+ sProgramProjectionMap.put(Programs.COLUMN_SEARCHABLE, Programs.COLUMN_SEARCHABLE);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_RECORDING_PROHIBITED, Programs.COLUMN_RECORDING_PROHIBITED);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_INTERNAL_PROVIDER_DATA, Programs.COLUMN_INTERNAL_PROVIDER_DATA);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_INTERNAL_PROVIDER_FLAG1, Programs.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_INTERNAL_PROVIDER_FLAG2, Programs.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_INTERNAL_PROVIDER_FLAG3, Programs.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_INTERNAL_PROVIDER_FLAG4, Programs.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ sProgramProjectionMap.put(Programs.COLUMN_VERSION_NUMBER, Programs.COLUMN_VERSION_NUMBER);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_REVIEW_RATING_STYLE, Programs.COLUMN_REVIEW_RATING_STYLE);
+ sProgramProjectionMap.put(Programs.COLUMN_REVIEW_RATING, Programs.COLUMN_REVIEW_RATING);
+
+ sWatchedProgramProjectionMap = new HashMap<>();
+ sWatchedProgramProjectionMap.put(WatchedPrograms._ID, WatchedPrograms._ID);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
+ WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
+ WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_CHANNEL_ID, WatchedPrograms.COLUMN_CHANNEL_ID);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_TITLE, WatchedPrograms.COLUMN_TITLE);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
+ WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_DESCRIPTION, WatchedPrograms.COLUMN_DESCRIPTION);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
+ WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
+ WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN);
+ sWatchedProgramProjectionMap.put(
+ WATCHED_PROGRAMS_COLUMN_CONSOLIDATED, WATCHED_PROGRAMS_COLUMN_CONSOLIDATED);
+
+ sRecordedProgramProjectionMap = new HashMap<>();
+ sRecordedProgramProjectionMap.put(RecordedPrograms._ID, RecordedPrograms._ID);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_PACKAGE_NAME, RecordedPrograms.COLUMN_PACKAGE_NAME);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INPUT_ID, RecordedPrograms.COLUMN_INPUT_ID);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_CHANNEL_ID, RecordedPrograms.COLUMN_CHANNEL_ID);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_TITLE, RecordedPrograms.COLUMN_TITLE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
+ RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_SEASON_TITLE, RecordedPrograms.COLUMN_SEASON_TITLE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
+ RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_EPISODE_TITLE, RecordedPrograms.COLUMN_EPISODE_TITLE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_BROADCAST_GENRE, RecordedPrograms.COLUMN_BROADCAST_GENRE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_CANONICAL_GENRE, RecordedPrograms.COLUMN_CANONICAL_GENRE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_SHORT_DESCRIPTION,
+ RecordedPrograms.COLUMN_SHORT_DESCRIPTION);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_LONG_DESCRIPTION, RecordedPrograms.COLUMN_LONG_DESCRIPTION);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_VIDEO_WIDTH, RecordedPrograms.COLUMN_VIDEO_WIDTH);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_VIDEO_HEIGHT, RecordedPrograms.COLUMN_VIDEO_HEIGHT);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_AUDIO_LANGUAGE, RecordedPrograms.COLUMN_AUDIO_LANGUAGE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_CONTENT_RATING, RecordedPrograms.COLUMN_CONTENT_RATING);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_POSTER_ART_URI, RecordedPrograms.COLUMN_POSTER_ART_URI);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_THUMBNAIL_URI, RecordedPrograms.COLUMN_THUMBNAIL_URI);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_SEARCHABLE, RecordedPrograms.COLUMN_SEARCHABLE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_RECORDING_DATA_URI,
+ RecordedPrograms.COLUMN_RECORDING_DATA_URI);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_RECORDING_DATA_BYTES,
+ RecordedPrograms.COLUMN_RECORDING_DATA_BYTES);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
+ RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_VERSION_NUMBER, RecordedPrograms.COLUMN_VERSION_NUMBER);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_REVIEW_RATING_STYLE,
+ RecordedPrograms.COLUMN_REVIEW_RATING_STYLE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_REVIEW_RATING, RecordedPrograms.COLUMN_REVIEW_RATING);
+
+ sPreviewProgramProjectionMap = new HashMap<>();
+ sPreviewProgramProjectionMap.put(PreviewPrograms._ID, PreviewPrograms._ID);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_PACKAGE_NAME, PreviewPrograms.COLUMN_PACKAGE_NAME);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_CHANNEL_ID, PreviewPrograms.COLUMN_CHANNEL_ID);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_TITLE, PreviewPrograms.COLUMN_TITLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
+ PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_SEASON_TITLE, PreviewPrograms.COLUMN_SEASON_TITLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
+ PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_EPISODE_TITLE, PreviewPrograms.COLUMN_EPISODE_TITLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_CANONICAL_GENRE, PreviewPrograms.COLUMN_CANONICAL_GENRE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_SHORT_DESCRIPTION, PreviewPrograms.COLUMN_SHORT_DESCRIPTION);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_LONG_DESCRIPTION, PreviewPrograms.COLUMN_LONG_DESCRIPTION);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_VIDEO_WIDTH, PreviewPrograms.COLUMN_VIDEO_WIDTH);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_VIDEO_HEIGHT, PreviewPrograms.COLUMN_VIDEO_HEIGHT);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_AUDIO_LANGUAGE, PreviewPrograms.COLUMN_AUDIO_LANGUAGE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_CONTENT_RATING, PreviewPrograms.COLUMN_CONTENT_RATING);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_POSTER_ART_URI, PreviewPrograms.COLUMN_POSTER_ART_URI);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_THUMBNAIL_URI, PreviewPrograms.COLUMN_THUMBNAIL_URI);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_SEARCHABLE, PreviewPrograms.COLUMN_SEARCHABLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_VERSION_NUMBER, PreviewPrograms.COLUMN_VERSION_NUMBER);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI, PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS,
+ PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_DURATION_MILLIS, PreviewPrograms.COLUMN_DURATION_MILLIS);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTENT_URI, PreviewPrograms.COLUMN_INTENT_URI);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_WEIGHT, PreviewPrograms.COLUMN_WEIGHT);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_TRANSIENT, PreviewPrograms.COLUMN_TRANSIENT);
+ sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_TYPE, PreviewPrograms.COLUMN_TYPE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO,
+ PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO,
+ PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_LOGO_URI, PreviewPrograms.COLUMN_LOGO_URI);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_AVAILABILITY, PreviewPrograms.COLUMN_AVAILABILITY);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_STARTING_PRICE, PreviewPrograms.COLUMN_STARTING_PRICE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_OFFER_PRICE, PreviewPrograms.COLUMN_OFFER_PRICE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_RELEASE_DATE, PreviewPrograms.COLUMN_RELEASE_DATE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_ITEM_COUNT, PreviewPrograms.COLUMN_ITEM_COUNT);
+ sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LIVE, PreviewPrograms.COLUMN_LIVE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERACTION_TYPE, PreviewPrograms.COLUMN_INTERACTION_TYPE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERACTION_COUNT, PreviewPrograms.COLUMN_INTERACTION_COUNT);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_AUTHOR, PreviewPrograms.COLUMN_AUTHOR);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_REVIEW_RATING_STYLE,
+ PreviewPrograms.COLUMN_REVIEW_RATING_STYLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_REVIEW_RATING, PreviewPrograms.COLUMN_REVIEW_RATING);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_BROWSABLE, PreviewPrograms.COLUMN_BROWSABLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_CONTENT_ID, PreviewPrograms.COLUMN_CONTENT_ID);
+
+ sWatchNextProgramProjectionMap = new HashMap<>();
+ sWatchNextProgramProjectionMap.put(WatchNextPrograms._ID, WatchNextPrograms._ID);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_PACKAGE_NAME, WatchNextPrograms.COLUMN_PACKAGE_NAME);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_TITLE, WatchNextPrograms.COLUMN_TITLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
+ WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_SEASON_TITLE, WatchNextPrograms.COLUMN_SEASON_TITLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
+ WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_EPISODE_TITLE, WatchNextPrograms.COLUMN_EPISODE_TITLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_CANONICAL_GENRE, WatchNextPrograms.COLUMN_CANONICAL_GENRE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_SHORT_DESCRIPTION,
+ WatchNextPrograms.COLUMN_SHORT_DESCRIPTION);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_LONG_DESCRIPTION,
+ WatchNextPrograms.COLUMN_LONG_DESCRIPTION);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_VIDEO_WIDTH, WatchNextPrograms.COLUMN_VIDEO_WIDTH);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_VIDEO_HEIGHT, WatchNextPrograms.COLUMN_VIDEO_HEIGHT);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_AUDIO_LANGUAGE, WatchNextPrograms.COLUMN_AUDIO_LANGUAGE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_CONTENT_RATING, WatchNextPrograms.COLUMN_CONTENT_RATING);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_POSTER_ART_URI, WatchNextPrograms.COLUMN_POSTER_ART_URI);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_THUMBNAIL_URI, WatchNextPrograms.COLUMN_THUMBNAIL_URI);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_SEARCHABLE, WatchNextPrograms.COLUMN_SEARCHABLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_VERSION_NUMBER, WatchNextPrograms.COLUMN_VERSION_NUMBER);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI,
+ WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS,
+ WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_DURATION_MILLIS, WatchNextPrograms.COLUMN_DURATION_MILLIS);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTENT_URI, WatchNextPrograms.COLUMN_INTENT_URI);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_TRANSIENT, WatchNextPrograms.COLUMN_TRANSIENT);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_TYPE, WatchNextPrograms.COLUMN_TYPE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE, WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO,
+ WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO,
+ WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_LOGO_URI, WatchNextPrograms.COLUMN_LOGO_URI);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_AVAILABILITY, WatchNextPrograms.COLUMN_AVAILABILITY);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_STARTING_PRICE, WatchNextPrograms.COLUMN_STARTING_PRICE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_OFFER_PRICE, WatchNextPrograms.COLUMN_OFFER_PRICE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_RELEASE_DATE, WatchNextPrograms.COLUMN_RELEASE_DATE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_ITEM_COUNT, WatchNextPrograms.COLUMN_ITEM_COUNT);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_LIVE, WatchNextPrograms.COLUMN_LIVE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERACTION_TYPE,
+ WatchNextPrograms.COLUMN_INTERACTION_TYPE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERACTION_COUNT,
+ WatchNextPrograms.COLUMN_INTERACTION_COUNT);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_AUTHOR, WatchNextPrograms.COLUMN_AUTHOR);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE,
+ WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_REVIEW_RATING, WatchNextPrograms.COLUMN_REVIEW_RATING);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_BROWSABLE, WatchNextPrograms.COLUMN_BROWSABLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_CONTENT_ID, WatchNextPrograms.COLUMN_CONTENT_ID);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS,
+ WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS);
+ }
+
+ // Mapping from broadcast genre to canonical genre.
+ private static Map<String, String> sGenreMap;
+
+ private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
+
+ private static final String PERMISSION_ACCESS_ALL_EPG_DATA =
+ "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA";
+
+ private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
+ "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";
+
+ private static final String CREATE_RECORDED_PROGRAMS_TABLE_SQL =
+ "CREATE TABLE "
+ + RECORDED_PROGRAMS_TABLE
+ + " ("
+ + RecordedPrograms._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + RecordedPrograms.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + RecordedPrograms.COLUMN_INPUT_ID
+ + " TEXT NOT NULL,"
+ + RecordedPrograms.COLUMN_CHANNEL_ID
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_TITLE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_SEASON_TITLE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_EPISODE_TITLE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_BROADCAST_GENRE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_CANONICAL_GENRE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_SHORT_DESCRIPTION
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_LONG_DESCRIPTION
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_VIDEO_WIDTH
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_VIDEO_HEIGHT
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_AUDIO_LANGUAGE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_CONTENT_RATING
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_POSTER_ART_URI
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_THUMBNAIL_URI
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + RecordedPrograms.COLUMN_RECORDING_DATA_URI
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_RECORDING_DATA_BYTES
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA
+ + " BLOB,"
+ + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_VERSION_NUMBER
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_REVIEW_RATING
+ + " TEXT,"
+ + "FOREIGN KEY("
+ + RecordedPrograms.COLUMN_CHANNEL_ID
+ + ") "
+ + "REFERENCES "
+ + CHANNELS_TABLE
+ + "("
+ + Channels._ID
+ + ") "
+ + "ON UPDATE CASCADE ON DELETE SET NULL);";
+
+ private static final String CREATE_PREVIEW_PROGRAMS_TABLE_SQL =
+ "CREATE TABLE "
+ + PREVIEW_PROGRAMS_TABLE
+ + " ("
+ + PreviewPrograms._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + PreviewPrograms.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + PreviewPrograms.COLUMN_CHANNEL_ID
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_TITLE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_SEASON_TITLE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_EPISODE_TITLE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_CANONICAL_GENRE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_SHORT_DESCRIPTION
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_LONG_DESCRIPTION
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_VIDEO_WIDTH
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_VIDEO_HEIGHT
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_AUDIO_LANGUAGE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_CONTENT_RATING
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_POSTER_ART_URI
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_THUMBNAIL_URI
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA
+ + " BLOB,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_VERSION_NUMBER
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_DURATION_MILLIS
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTENT_URI
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_WEIGHT
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_TRANSIENT
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + PreviewPrograms.COLUMN_TYPE
+ + " INTEGER NOT NULL,"
+ + PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_LOGO_URI
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_AVAILABILITY
+ + " INTERGER,"
+ + PreviewPrograms.COLUMN_STARTING_PRICE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_OFFER_PRICE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_RELEASE_DATE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_ITEM_COUNT
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_LIVE
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + PreviewPrograms.COLUMN_INTERACTION_TYPE
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTERACTION_COUNT
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_AUTHOR
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_REVIEW_RATING
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_BROWSABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + PreviewPrograms.COLUMN_CONTENT_ID
+ + " TEXT,"
+ + "FOREIGN KEY("
+ + PreviewPrograms.COLUMN_CHANNEL_ID
+ + ","
+ + PreviewPrograms.COLUMN_PACKAGE_NAME
+ + ") REFERENCES "
+ + CHANNELS_TABLE
+ + "("
+ + Channels._ID
+ + ","
+ + Channels.COLUMN_PACKAGE_NAME
+ + ") ON UPDATE CASCADE ON DELETE CASCADE"
+ + ");";
+ private static final String CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL =
+ "CREATE INDEX preview_programs_package_name_index ON "
+ + PREVIEW_PROGRAMS_TABLE
+ + "("
+ + PreviewPrograms.COLUMN_PACKAGE_NAME
+ + ");";
+ private static final String CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL =
+ "CREATE INDEX preview_programs_id_index ON "
+ + PREVIEW_PROGRAMS_TABLE
+ + "("
+ + PreviewPrograms.COLUMN_CHANNEL_ID
+ + ");";
+ private static final String CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL =
+ "CREATE TABLE "
+ + WATCH_NEXT_PROGRAMS_TABLE
+ + " ("
+ + WatchNextPrograms._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + WatchNextPrograms.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + WatchNextPrograms.COLUMN_TITLE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_SEASON_TITLE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_EPISODE_TITLE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_CANONICAL_GENRE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_SHORT_DESCRIPTION
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_LONG_DESCRIPTION
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_VIDEO_WIDTH
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_VIDEO_HEIGHT
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_AUDIO_LANGUAGE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_CONTENT_RATING
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_POSTER_ART_URI
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_THUMBNAIL_URI
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA
+ + " BLOB,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_VERSION_NUMBER
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_DURATION_MILLIS
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTENT_URI
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_TRANSIENT
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + WatchNextPrograms.COLUMN_TYPE
+ + " INTEGER NOT NULL,"
+ + WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_LOGO_URI
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_AVAILABILITY
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_STARTING_PRICE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_OFFER_PRICE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_RELEASE_DATE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_ITEM_COUNT
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_LIVE
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + WatchNextPrograms.COLUMN_INTERACTION_TYPE
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTERACTION_COUNT
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_AUTHOR
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_REVIEW_RATING
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_BROWSABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + WatchNextPrograms.COLUMN_CONTENT_ID
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS
+ + " INTEGER"
+ + ");";
+ private static final String CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL =
+ "CREATE INDEX watch_next_programs_package_name_index ON "
+ + WATCH_NEXT_PROGRAMS_TABLE
+ + "("
+ + WatchNextPrograms.COLUMN_PACKAGE_NAME
+ + ");";
+
+ private String mCallingPackage = "com.android.tv";
+
+ static class DatabaseHelper extends SQLiteOpenHelper {
+ private Context mContext;
+
+ public static synchronized DatabaseHelper createInstance(Context context) {
+ return new DatabaseHelper(context);
+ }
+
+ private DatabaseHelper(Context context) {
+ this(context, DATABASE_NAME, DATABASE_VERSION);
+ }
+
+ @VisibleForTesting
+ DatabaseHelper(Context context, String databaseName, int databaseVersion) {
+ super(context, databaseName, null, databaseVersion);
+ mContext = context;
+ }
+
+ @Override
+ public void onConfigure(SQLiteDatabase db) {
+ db.setForeignKeyConstraintsEnabled(true);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ if (DEBUG) {
+ Log.d(TAG, "Creating database");
+ }
+ // Set up the database schema.
+ db.execSQL(
+ "CREATE TABLE "
+ + CHANNELS_TABLE
+ + " ("
+ + Channels._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + Channels.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + Channels.COLUMN_INPUT_ID
+ + " TEXT NOT NULL,"
+ + Channels.COLUMN_TYPE
+ + " TEXT NOT NULL DEFAULT '"
+ + Channels.TYPE_OTHER
+ + "',"
+ + Channels.COLUMN_SERVICE_TYPE
+ + " TEXT NOT NULL DEFAULT '"
+ + Channels.SERVICE_TYPE_AUDIO_VIDEO
+ + "',"
+ + Channels.COLUMN_ORIGINAL_NETWORK_ID
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_TRANSPORT_STREAM_ID
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_SERVICE_ID
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_DISPLAY_NUMBER
+ + " TEXT,"
+ + Channels.COLUMN_DISPLAY_NAME
+ + " TEXT,"
+ + Channels.COLUMN_NETWORK_AFFILIATION
+ + " TEXT,"
+ + Channels.COLUMN_DESCRIPTION
+ + " TEXT,"
+ + Channels.COLUMN_VIDEO_FORMAT
+ + " TEXT,"
+ + Channels.COLUMN_BROWSABLE
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + Channels.COLUMN_LOCKED
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_APP_LINK_ICON_URI
+ + " TEXT,"
+ + Channels.COLUMN_APP_LINK_POSTER_ART_URI
+ + " TEXT,"
+ + Channels.COLUMN_APP_LINK_TEXT
+ + " TEXT,"
+ + Channels.COLUMN_APP_LINK_COLOR
+ + " INTEGER,"
+ + Channels.COLUMN_APP_LINK_INTENT_URI
+ + " TEXT,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_DATA
+ + " BLOB,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER,"
+ + CHANNELS_COLUMN_LOGO
+ + " BLOB,"
+ + Channels.COLUMN_VERSION_NUMBER
+ + " INTEGER,"
+ + Channels.COLUMN_TRANSIENT
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_ID
+ + " TEXT,"
+ // Needed for foreign keys in other tables.
+ + "UNIQUE("
+ + Channels._ID
+ + ","
+ + Channels.COLUMN_PACKAGE_NAME
+ + ")"
+ + ");");
+ db.execSQL(
+ "CREATE TABLE "
+ + PROGRAMS_TABLE
+ + " ("
+ + Programs._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + Programs.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + Programs.COLUMN_CHANNEL_ID
+ + " INTEGER,"
+ + Programs.COLUMN_TITLE
+ + " TEXT,"
+ + Programs.COLUMN_SEASON_DISPLAY_NUMBER
+ + " TEXT,"
+ + Programs.COLUMN_SEASON_TITLE
+ + " TEXT,"
+ + Programs.COLUMN_EPISODE_DISPLAY_NUMBER
+ + " TEXT,"
+ + Programs.COLUMN_EPISODE_TITLE
+ + " TEXT,"
+ + Programs.COLUMN_START_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + Programs.COLUMN_END_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + Programs.COLUMN_BROADCAST_GENRE
+ + " TEXT,"
+ + Programs.COLUMN_CANONICAL_GENRE
+ + " TEXT,"
+ + Programs.COLUMN_SHORT_DESCRIPTION
+ + " TEXT,"
+ + Programs.COLUMN_LONG_DESCRIPTION
+ + " TEXT,"
+ + Programs.COLUMN_VIDEO_WIDTH
+ + " INTEGER,"
+ + Programs.COLUMN_VIDEO_HEIGHT
+ + " INTEGER,"
+ + Programs.COLUMN_AUDIO_LANGUAGE
+ + " TEXT,"
+ + Programs.COLUMN_CONTENT_RATING
+ + " TEXT,"
+ + Programs.COLUMN_POSTER_ART_URI
+ + " TEXT,"
+ + Programs.COLUMN_THUMBNAIL_URI
+ + " TEXT,"
+ + Programs.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + Programs.COLUMN_RECORDING_PROHIBITED
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Programs.COLUMN_INTERNAL_PROVIDER_DATA
+ + " BLOB,"
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER,"
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER,"
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER,"
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER,"
+ + Programs.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER,"
+ + Programs.COLUMN_REVIEW_RATING
+ + " TEXT,"
+ + Programs.COLUMN_VERSION_NUMBER
+ + " INTEGER,"
+ + "FOREIGN KEY("
+ + Programs.COLUMN_CHANNEL_ID
+ + ","
+ + Programs.COLUMN_PACKAGE_NAME
+ + ") REFERENCES "
+ + CHANNELS_TABLE
+ + "("
+ + Channels._ID
+ + ","
+ + Channels.COLUMN_PACKAGE_NAME
+ + ") ON UPDATE CASCADE ON DELETE CASCADE"
+ + ");");
+ db.execSQL(
+ "CREATE INDEX "
+ + PROGRAMS_TABLE_PACKAGE_NAME_INDEX
+ + " ON "
+ + PROGRAMS_TABLE
+ + "("
+ + Programs.COLUMN_PACKAGE_NAME
+ + ");");
+ db.execSQL(
+ "CREATE INDEX "
+ + PROGRAMS_TABLE_CHANNEL_ID_INDEX
+ + " ON "
+ + PROGRAMS_TABLE
+ + "("
+ + Programs.COLUMN_CHANNEL_ID
+ + ");");
+ db.execSQL(
+ "CREATE INDEX "
+ + PROGRAMS_TABLE_START_TIME_INDEX
+ + " ON "
+ + PROGRAMS_TABLE
+ + "("
+ + Programs.COLUMN_START_TIME_UTC_MILLIS
+ + ");");
+ db.execSQL(
+ "CREATE INDEX "
+ + PROGRAMS_TABLE_END_TIME_INDEX
+ + " ON "
+ + PROGRAMS_TABLE
+ + "("
+ + Programs.COLUMN_END_TIME_UTC_MILLIS
+ + ");");
+ db.execSQL(
+ "CREATE TABLE "
+ + WATCHED_PROGRAMS_TABLE
+ + " ("
+ + WatchedPrograms._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + WatchedPrograms.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + WatchedPrograms.COLUMN_CHANNEL_ID
+ + " INTEGER,"
+ + WatchedPrograms.COLUMN_TITLE
+ + " TEXT,"
+ + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + WatchedPrograms.COLUMN_DESCRIPTION
+ + " TEXT,"
+ + WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS
+ + " TEXT,"
+ + WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN
+ + " TEXT NOT NULL,"
+ + WATCHED_PROGRAMS_COLUMN_CONSOLIDATED
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + "FOREIGN KEY("
+ + WatchedPrograms.COLUMN_CHANNEL_ID
+ + ","
+ + WatchedPrograms.COLUMN_PACKAGE_NAME
+ + ") REFERENCES "
+ + CHANNELS_TABLE
+ + "("
+ + Channels._ID
+ + ","
+ + Channels.COLUMN_PACKAGE_NAME
+ + ") ON UPDATE CASCADE ON DELETE CASCADE"
+ + ");");
+ db.execSQL(
+ "CREATE INDEX "
+ + WATCHED_PROGRAMS_TABLE_CHANNEL_ID_INDEX
+ + " ON "
+ + WATCHED_PROGRAMS_TABLE
+ + "("
+ + WatchedPrograms.COLUMN_CHANNEL_ID
+ + ");");
+ db.execSQL(CREATE_RECORDED_PROGRAMS_TABLE_SQL);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_TABLE_SQL);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL);
+ db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL);
+ db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < 23) {
+ Log.i(
+ TAG,
+ "Upgrading from version "
+ + oldVersion
+ + " to "
+ + newVersion
+ + ", data will be lost!");
+ db.execSQL("DROP TABLE IF EXISTS " + DELETED_CHANNELS_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + WATCHED_PROGRAMS_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + PROGRAMS_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + CHANNELS_TABLE);
+
+ onCreate(db);
+ return;
+ }
+
+ Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion + ".");
+ if (oldVersion <= 23) {
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER;");
+ }
+ if (oldVersion <= 24) {
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER;");
+ }
+ if (oldVersion <= 25) {
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_APP_LINK_ICON_URI
+ + " TEXT;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_APP_LINK_POSTER_ART_URI
+ + " TEXT;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_APP_LINK_TEXT
+ + " TEXT;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_APP_LINK_COLOR
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_APP_LINK_INTENT_URI
+ + " TEXT;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1;");
+ }
+ if (oldVersion <= 28) {
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_SEASON_TITLE
+ + " TEXT;");
+ migrateIntegerColumnToTextColumn(
+ db,
+ PROGRAMS_TABLE,
+ Programs.COLUMN_SEASON_NUMBER,
+ Programs.COLUMN_SEASON_DISPLAY_NUMBER);
+ migrateIntegerColumnToTextColumn(
+ db,
+ PROGRAMS_TABLE,
+ Programs.COLUMN_EPISODE_NUMBER,
+ Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
+ }
+ if (oldVersion <= 29) {
+ db.execSQL("DROP TABLE IF EXISTS " + RECORDED_PROGRAMS_TABLE);
+ db.execSQL(CREATE_RECORDED_PROGRAMS_TABLE_SQL);
+ }
+ if (oldVersion <= 30) {
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_RECORDING_PROHIBITED
+ + " INTEGER NOT NULL DEFAULT 0;");
+ }
+ if (oldVersion <= 32) {
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_TRANSIENT
+ + " INTEGER NOT NULL DEFAULT 0;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_INTERNAL_PROVIDER_ID
+ + " TEXT;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_REVIEW_RATING
+ + " TEXT;");
+ if (oldVersion > 29) {
+ db.execSQL(
+ "ALTER TABLE "
+ + RECORDED_PROGRAMS_TABLE
+ + " ADD "
+ + RecordedPrograms.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + RECORDED_PROGRAMS_TABLE
+ + " ADD "
+ + RecordedPrograms.COLUMN_REVIEW_RATING
+ + " TEXT;");
+ }
+ }
+ if (oldVersion <= 33) {
+ db.execSQL("DROP TABLE IF EXISTS " + PREVIEW_PROGRAMS_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + WATCH_NEXT_PROGRAMS_TABLE);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_TABLE_SQL);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL);
+ db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL);
+ db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL);
+ }
+ Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion + " is done.");
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ // Call a static method on the TvProvider because changes to sInitialized must
+ // be guarded by a lock on the class.
+ initOnOpenIfNeeded(mContext, db);
+ }
+
+ private static void migrateIntegerColumnToTextColumn(
+ SQLiteDatabase db, String table, String integerColumn, String textColumn) {
+ db.execSQL("ALTER TABLE " + table + " ADD " + textColumn + " TEXT;");
+ db.execSQL(
+ "UPDATE "
+ + table
+ + " SET "
+ + textColumn
+ + " = CAST("
+ + integerColumn
+ + " AS TEXT);");
+ }
+ }
+
+ private DatabaseHelper mOpenHelper;
+ private static SharedPreferences sBlockedPackagesSharedPreference;
+ private static Map<String, Boolean> sBlockedPackages;
+
+ @Override
+ public boolean onCreate() {
+ if (DEBUG) {
+ Log.d(TAG, "Creating TvProvider");
+ }
+ mOpenHelper = DatabaseHelper.createInstance(getContext());
+ return true;
+ }
+
+ @VisibleForTesting
+ String getCallingPackage_() {
+ return mCallingPackage;
+ }
+
+ public void setCallingPackage(String packageName) {
+ mCallingPackage = packageName;
+ }
+
+ void setOpenHelper(DatabaseHelper helper) {
+ mOpenHelper = helper;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ switch (sUriMatcher.match(uri)) {
+ case MATCH_CHANNEL:
+ return Channels.CONTENT_TYPE;
+ case MATCH_CHANNEL_ID:
+ return Channels.CONTENT_ITEM_TYPE;
+ case MATCH_CHANNEL_ID_LOGO:
+ return "image/png";
+ case MATCH_PASSTHROUGH_ID:
+ return Channels.CONTENT_ITEM_TYPE;
+ case MATCH_PROGRAM:
+ return Programs.CONTENT_TYPE;
+ case MATCH_PROGRAM_ID:
+ return Programs.CONTENT_ITEM_TYPE;
+ case MATCH_WATCHED_PROGRAM:
+ return WatchedPrograms.CONTENT_TYPE;
+ case MATCH_WATCHED_PROGRAM_ID:
+ return WatchedPrograms.CONTENT_ITEM_TYPE;
+ case MATCH_RECORDED_PROGRAM:
+ return RecordedPrograms.CONTENT_TYPE;
+ case MATCH_RECORDED_PROGRAM_ID:
+ return RecordedPrograms.CONTENT_ITEM_TYPE;
+ case MATCH_PREVIEW_PROGRAM:
+ return PreviewPrograms.CONTENT_TYPE;
+ case MATCH_PREVIEW_PROGRAM_ID:
+ return PreviewPrograms.CONTENT_ITEM_TYPE;
+ case MATCH_WATCH_NEXT_PROGRAM:
+ return WatchNextPrograms.CONTENT_TYPE;
+ case MATCH_WATCH_NEXT_PROGRAM_ID:
+ return WatchNextPrograms.CONTENT_ITEM_TYPE;
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ ensureInitialized();
+ boolean needsToValidateSortOrder = !callerHasAccessAllEpgDataPermission();
+ SqlParams params = createSqlParams(OP_QUERY, uri, selection, selectionArgs);
+
+ SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+ queryBuilder.setStrict(needsToValidateSortOrder);
+ queryBuilder.setTables(params.getTables());
+ String orderBy = null;
+ Map<String, String> projectionMap;
+ switch (params.getTables()) {
+ case PROGRAMS_TABLE:
+ projectionMap = sProgramProjectionMap;
+ orderBy = DEFAULT_PROGRAMS_SORT_ORDER;
+ break;
+ case WATCHED_PROGRAMS_TABLE:
+ projectionMap = sWatchedProgramProjectionMap;
+ orderBy = DEFAULT_WATCHED_PROGRAMS_SORT_ORDER;
+ break;
+ case RECORDED_PROGRAMS_TABLE:
+ projectionMap = sRecordedProgramProjectionMap;
+ break;
+ case PREVIEW_PROGRAMS_TABLE:
+ projectionMap = sPreviewProgramProjectionMap;
+ break;
+ case WATCH_NEXT_PROGRAMS_TABLE:
+ projectionMap = sWatchNextProgramProjectionMap;
+ break;
+ default:
+ projectionMap = sChannelProjectionMap;
+ break;
+ }
+ queryBuilder.setProjectionMap(createProjectionMapForQuery(projection, projectionMap));
+ if (needsToValidateSortOrder) {
+ validateSortOrder(sortOrder, projectionMap.keySet());
+ }
+
+ // Use the default sort order only if no sort order is specified.
+ if (!TextUtils.isEmpty(sortOrder)) {
+ orderBy = sortOrder;
+ }
+
+ // Get the database and run the query.
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor c =
+ queryBuilder.query(
+ db,
+ projection,
+ params.getSelection(),
+ params.getSelectionArgs(),
+ null,
+ null,
+ orderBy);
+
+ // Tell the cursor what URI to watch, so it knows when its source data changes.
+ c.setNotificationUri(getContext().getContentResolver(), uri);
+ return c;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ ensureInitialized();
+ switch (sUriMatcher.match(uri)) {
+ case MATCH_CHANNEL:
+ // Preview channels are not necessarily associated with TV input service.
+ // Therefore, we fill a fake ID to meet not null restriction for preview channels.
+ if (values.get(Channels.COLUMN_INPUT_ID) == null
+ && TvContractCompat.PARAM_CHANNEL.equals(
+ values.get(Channels.COLUMN_TYPE))) {
+ values.put(Channels.COLUMN_INPUT_ID, EMPTY_STRING);
+ }
+ filterContentValues(values, sChannelProjectionMap);
+ return insertChannel(uri, values);
+ case MATCH_PROGRAM:
+ filterContentValues(values, sProgramProjectionMap);
+ return insertProgram(uri, values);
+ case MATCH_WATCHED_PROGRAM:
+ return insertWatchedProgram(uri, values);
+ case MATCH_RECORDED_PROGRAM:
+ filterContentValues(values, sRecordedProgramProjectionMap);
+ return insertRecordedProgram(uri, values);
+ case MATCH_PREVIEW_PROGRAM:
+ filterContentValues(values, sPreviewProgramProjectionMap);
+ return insertPreviewProgram(uri, values);
+ case MATCH_WATCH_NEXT_PROGRAM:
+ filterContentValues(values, sWatchNextProgramProjectionMap);
+ return insertWatchNextProgram(uri, values);
+ case MATCH_CHANNEL_ID:
+ case MATCH_CHANNEL_ID_LOGO:
+ case MATCH_PASSTHROUGH_ID:
+ case MATCH_PROGRAM_ID:
+ case MATCH_WATCHED_PROGRAM_ID:
+ case MATCH_RECORDED_PROGRAM_ID:
+ case MATCH_PREVIEW_PROGRAM_ID:
+ throw new UnsupportedOperationException("Cannot insert into that URI: " + uri);
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ }
+
+ private Uri insertChannel(Uri uri, ContentValues values) {
+ if (TextUtils.equals(
+ values.getAsString(Channels.COLUMN_TYPE), TvContractCompat.Channels.TYPE_PREVIEW)) {
+ blockIllegalAccessFromBlockedPackage();
+ }
+ // Mark the owner package of this channel.
+ values.put(Channels.COLUMN_PACKAGE_NAME, getCallingPackage_());
+ blockIllegalAccessToChannelsSystemColumns(values);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(CHANNELS_TABLE, null, values);
+ if (rowId > 0) {
+ Uri channelUri = TvContractCompat.buildChannelUri(rowId);
+ notifyChange(channelUri);
+ return channelUri;
+ }
+
+ throw new SQLException("Failed to insert row into " + uri);
+ }
+
+ private Uri insertProgram(Uri uri, ContentValues values) {
+ if (!callerHasAccessAllEpgDataPermission()
+ || !values.containsKey(Programs.COLUMN_PACKAGE_NAME)) {
+ // Mark the owner package of this program. System app with a proper permission may
+ // change the owner of the program.
+ values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_());
+ }
+
+ checkAndConvertGenre(values);
+ checkAndConvertDeprecatedColumns(values);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(PROGRAMS_TABLE, null, values);
+ if (rowId > 0) {
+ Uri programUri = TvContractCompat.buildProgramUri(rowId);
+ notifyChange(programUri);
+ return programUri;
+ }
+
+ throw new SQLException("Failed to insert row into " + uri);
+ }
+
+ private Uri insertWatchedProgram(Uri uri, ContentValues values) {
+ if (DEBUG) {
+ Log.d(TAG, "insertWatchedProgram(uri=" + uri + ", values={" + values + "})");
+ }
+ Long watchStartTime = values.getAsLong(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS);
+ Long watchEndTime = values.getAsLong(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS);
+ // The system sends only two kinds of watch events:
+ // 1. The user tunes to a new channel. (COLUMN_WATCH_START_TIME_UTC_MILLIS)
+ // 2. The user stops watching. (COLUMN_WATCH_END_TIME_UTC_MILLIS)
+ if (watchStartTime != null && watchEndTime == null) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(WATCHED_PROGRAMS_TABLE, null, values);
+ if (rowId > 0) {
+ return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, rowId);
+ }
+ Log.w(TAG, "Failed to insert row for " + values + ". Channel does not exist.");
+ return null;
+ } else if (watchStartTime == null && watchEndTime != null) {
+ return null;
+ }
+ // All the other cases are invalid.
+ throw new IllegalArgumentException(
+ "Only one of COLUMN_WATCH_START_TIME_UTC_MILLIS and"
+ + " COLUMN_WATCH_END_TIME_UTC_MILLIS should be specified");
+ }
+
+ private Uri insertRecordedProgram(Uri uri, ContentValues values) {
+ // Mark the owner package of this program.
+ values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_());
+
+ checkAndConvertGenre(values);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(RECORDED_PROGRAMS_TABLE, null, values);
+ if (rowId > 0) {
+ Uri recordedProgramUri = TvContractCompat.buildRecordedProgramUri(rowId);
+ notifyChange(recordedProgramUri);
+ return recordedProgramUri;
+ }
+
+ throw new SQLException("Failed to insert row into " + uri);
+ }
+
+ private Uri insertPreviewProgram(Uri uri, ContentValues values) {
+ if (!values.containsKey(PreviewPrograms.COLUMN_TYPE)) {
+ throw new IllegalArgumentException(
+ "Missing the required column: " + PreviewPrograms.COLUMN_TYPE);
+ }
+ blockIllegalAccessFromBlockedPackage();
+ // Mark the owner package of this program.
+ values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_());
+ blockIllegalAccessToPreviewProgramsSystemColumns(values);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(PREVIEW_PROGRAMS_TABLE, null, values);
+ if (rowId > 0) {
+ Uri previewProgramUri = TvContractCompat.buildPreviewProgramUri(rowId);
+ notifyChange(previewProgramUri);
+ return previewProgramUri;
+ }
+
+ throw new SQLException("Failed to insert row into " + uri);
+ }
+
+ private Uri insertWatchNextProgram(Uri uri, ContentValues values) {
+ if (!values.containsKey(WatchNextPrograms.COLUMN_TYPE)) {
+ throw new IllegalArgumentException(
+ "Missing the required column: " + WatchNextPrograms.COLUMN_TYPE);
+ }
+ blockIllegalAccessFromBlockedPackage();
+ if (!callerHasAccessAllEpgDataPermission()
+ || !values.containsKey(Programs.COLUMN_PACKAGE_NAME)) {
+ // Mark the owner package of this program. System app with a proper permission may
+ // change the owner of the program.
+ values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_());
+ }
+ blockIllegalAccessToPreviewProgramsSystemColumns(values);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(WATCH_NEXT_PROGRAMS_TABLE, null, values);
+ if (rowId > 0) {
+ Uri watchNextProgramUri = TvContractCompat.buildWatchNextProgramUri(rowId);
+ notifyChange(watchNextProgramUri);
+ return watchNextProgramUri;
+ }
+
+ throw new SQLException("Failed to insert row into " + uri);
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ SqlParams params = createSqlParams(OP_DELETE, uri, selection, selectionArgs);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int count;
+ switch (sUriMatcher.match(uri)) {
+ case MATCH_CHANNEL_ID_LOGO:
+ ContentValues values = new ContentValues();
+ values.putNull(CHANNELS_COLUMN_LOGO);
+ count =
+ db.update(
+ params.getTables(),
+ values,
+ params.getSelection(),
+ params.getSelectionArgs());
+ break;
+ case MATCH_CHANNEL:
+ case MATCH_PROGRAM:
+ case MATCH_WATCHED_PROGRAM:
+ case MATCH_RECORDED_PROGRAM:
+ case MATCH_PREVIEW_PROGRAM:
+ case MATCH_WATCH_NEXT_PROGRAM:
+ case MATCH_CHANNEL_ID:
+ case MATCH_PASSTHROUGH_ID:
+ case MATCH_PROGRAM_ID:
+ case MATCH_WATCHED_PROGRAM_ID:
+ case MATCH_RECORDED_PROGRAM_ID:
+ case MATCH_PREVIEW_PROGRAM_ID:
+ case MATCH_WATCH_NEXT_PROGRAM_ID:
+ count =
+ db.delete(
+ params.getTables(),
+ params.getSelection(),
+ params.getSelectionArgs());
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ if (count > 0) {
+ notifyChange(uri);
+ }
+ return count;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ SqlParams params = createSqlParams(OP_UPDATE, uri, selection, selectionArgs);
+ blockIllegalAccessToIdAndPackageName(uri, values);
+ boolean containImmutableColumn = false;
+ if (params.getTables().equals(CHANNELS_TABLE)) {
+ filterContentValues(values, sChannelProjectionMap);
+ containImmutableColumn = disallowModifyChannelType(values, params);
+ if (containImmutableColumn && sUriMatcher.match(uri) != MATCH_CHANNEL_ID) {
+ Log.i(TAG, "Updating failed. Attempt to change immutable column for channels.");
+ return 0;
+ }
+ blockIllegalAccessToChannelsSystemColumns(values);
+ } else if (params.getTables().equals(PROGRAMS_TABLE)) {
+ filterContentValues(values, sProgramProjectionMap);
+ checkAndConvertGenre(values);
+ checkAndConvertDeprecatedColumns(values);
+ } else if (params.getTables().equals(RECORDED_PROGRAMS_TABLE)) {
+ filterContentValues(values, sRecordedProgramProjectionMap);
+ checkAndConvertGenre(values);
+ } else if (params.getTables().equals(PREVIEW_PROGRAMS_TABLE)) {
+ filterContentValues(values, sPreviewProgramProjectionMap);
+ containImmutableColumn = disallowModifyChannelId(values, params);
+ if (containImmutableColumn && PreviewPrograms.CONTENT_URI.equals(uri)) {
+ Log.i(
+ TAG,
+ "Updating failed. Attempt to change unmodifiable column for "
+ + "preview programs.");
+ return 0;
+ }
+ blockIllegalAccessToPreviewProgramsSystemColumns(values);
+ } else if (params.getTables().equals(WATCH_NEXT_PROGRAMS_TABLE)) {
+ filterContentValues(values, sWatchNextProgramProjectionMap);
+ blockIllegalAccessToPreviewProgramsSystemColumns(values);
+ }
+ if (values.size() == 0) {
+ // All values may be filtered out, no need to update
+ return 0;
+ }
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int count =
+ db.update(
+ params.getTables(),
+ values,
+ params.getSelection(),
+ params.getSelectionArgs());
+ if (count > 0) {
+ notifyChange(uri);
+ } else if (containImmutableColumn) {
+ Log.i(
+ TAG,
+ "Updating failed. The item may not exist or attempt to change "
+ + "immutable column.");
+ }
+ return count;
+ }
+
+ private synchronized void ensureInitialized() {
+ if (!sInitialized) {
+ // Database is not accessed before and the projection maps and the blocked package list
+ // are not updated yet. Gets database here to make it initialized.
+ mOpenHelper.getReadableDatabase();
+ }
+ }
+
+ private static synchronized void initOnOpenIfNeeded(Context context, SQLiteDatabase db) {
+ if (!sInitialized) {
+ updateProjectionMap(db, CHANNELS_TABLE, sChannelProjectionMap);
+ updateProjectionMap(db, PROGRAMS_TABLE, sProgramProjectionMap);
+ updateProjectionMap(db, WATCHED_PROGRAMS_TABLE, sWatchedProgramProjectionMap);
+ updateProjectionMap(db, RECORDED_PROGRAMS_TABLE, sRecordedProgramProjectionMap);
+ updateProjectionMap(db, PREVIEW_PROGRAMS_TABLE, sPreviewProgramProjectionMap);
+ updateProjectionMap(db, WATCH_NEXT_PROGRAMS_TABLE, sWatchNextProgramProjectionMap);
+ sBlockedPackagesSharedPreference =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ sBlockedPackages = new ConcurrentHashMap<>();
+ for (String packageName :
+ sBlockedPackagesSharedPreference.getStringSet(
+ SHARED_PREF_BLOCKED_PACKAGES_KEY, new HashSet<>())) {
+ sBlockedPackages.put(packageName, true);
+ }
+ sInitialized = true;
+ }
+ }
+
+ private static void updateProjectionMap(
+ SQLiteDatabase db, String tableName, Map<String, String> projectionMap) {
+ try (Cursor cursor = db.rawQuery("SELECT * FROM " + tableName + " LIMIT 0", null)) {
+ for (String columnName : cursor.getColumnNames()) {
+ if (!projectionMap.containsKey(columnName)) {
+ projectionMap.put(columnName, tableName + '.' + columnName);
+ }
+ }
+ }
+ }
+
+ private Map<String, String> createProjectionMapForQuery(
+ String[] projection, Map<String, String> projectionMap) {
+ if (projection == null) {
+ return projectionMap;
+ }
+ Map<String, String> columnProjectionMap = new HashMap<>();
+ for (String columnName : projection) {
+ // Value NULL will be provided if the requested column does not exist in the database.
+ columnProjectionMap.put(
+ columnName, projectionMap.getOrDefault(columnName, "NULL as " + columnName));
+ }
+ return columnProjectionMap;
+ }
+
+ private void filterContentValues(ContentValues values, Map<String, String> projectionMap) {
+ Iterator<String> iter = values.keySet().iterator();
+ while (iter.hasNext()) {
+ String columnName = iter.next();
+ if (!projectionMap.containsKey(columnName)) {
+ iter.remove();
+ }
+ }
+ }
+
+ private SqlParams createSqlParams(
+ String operation, Uri uri, String selection, String[] selectionArgs) {
+ int match = sUriMatcher.match(uri);
+ SqlParams params = new SqlParams(null, selection, selectionArgs);
+
+ // Control access to EPG data (excluding watched programs) when the caller doesn't have all
+ // access.
+ String prefix = match == MATCH_CHANNEL ? CHANNELS_TABLE + "." : "";
+ if (!callerHasAccessAllEpgDataPermission()
+ && match != MATCH_WATCHED_PROGRAM
+ && match != MATCH_WATCHED_PROGRAM_ID) {
+ if (!TextUtils.isEmpty(selection)) {
+ throw new SecurityException("Selection not allowed for " + uri);
+ }
+ // Limit the operation only to the data that the calling package owns except for when
+ // the caller tries to read TV listings and has the appropriate permission.
+ if (operation.equals(OP_QUERY) && callerHasReadTvListingsPermission()) {
+ params.setWhere(
+ prefix
+ + BaseTvColumns.COLUMN_PACKAGE_NAME
+ + "=? OR "
+ + Channels.COLUMN_SEARCHABLE
+ + "=?",
+ getCallingPackage_(),
+ "1");
+ } else {
+ params.setWhere(
+ prefix + BaseTvColumns.COLUMN_PACKAGE_NAME + "=?", getCallingPackage_());
+ }
+ }
+ String packageName = uri.getQueryParameter(PARAM_PACKAGE);
+ if (packageName != null) {
+ params.appendWhere(prefix + BaseTvColumns.COLUMN_PACKAGE_NAME + "=?", packageName);
+ }
+
+ switch (match) {
+ case MATCH_CHANNEL:
+ String genre = uri.getQueryParameter(TvContractCompat.PARAM_CANONICAL_GENRE);
+ if (genre == null) {
+ params.setTables(CHANNELS_TABLE);
+ } else {
+ if (!operation.equals(OP_QUERY)) {
+ throw new SecurityException(
+ capitalize(operation) + " not allowed for " + uri);
+ }
+ if (!Genres.isCanonical(genre)) {
+ throw new IllegalArgumentException("Not a canonical genre : " + genre);
+ }
+ params.setTables(CHANNELS_TABLE_INNER_JOIN_PROGRAMS_TABLE);
+ String curTime = String.valueOf(System.currentTimeMillis());
+ params.appendWhere(
+ "LIKE(?, "
+ + Programs.COLUMN_CANONICAL_GENRE
+ + ") AND "
+ + Programs.COLUMN_START_TIME_UTC_MILLIS
+ + "<=? AND "
+ + Programs.COLUMN_END_TIME_UTC_MILLIS
+ + ">=?",
+ "%" + genre + "%",
+ curTime,
+ curTime);
+ }
+ String inputId = uri.getQueryParameter(TvContractCompat.PARAM_INPUT);
+ if (inputId != null) {
+ params.appendWhere(Channels.COLUMN_INPUT_ID + "=?", inputId);
+ }
+ boolean browsableOnly =
+ uri.getBooleanQueryParameter(TvContractCompat.PARAM_BROWSABLE_ONLY, false);
+ if (browsableOnly) {
+ params.appendWhere(Channels.COLUMN_BROWSABLE + "=1");
+ }
+ String preview = uri.getQueryParameter(PARAM_PREVIEW);
+ if (preview != null) {
+ String previewSelection =
+ Channels.COLUMN_TYPE
+ + (preview.equals(String.valueOf(true)) ? "=?" : "!=?");
+ params.appendWhere(previewSelection, Channels.TYPE_PREVIEW);
+ }
+ break;
+ case MATCH_CHANNEL_ID:
+ params.setTables(CHANNELS_TABLE);
+ params.appendWhere(Channels._ID + "=?", uri.getLastPathSegment());
+ break;
+ case MATCH_PROGRAM:
+ params.setTables(PROGRAMS_TABLE);
+ String paramChannelId = uri.getQueryParameter(TvContractCompat.PARAM_CHANNEL);
+ if (paramChannelId != null) {
+ String channelId = String.valueOf(Long.parseLong(paramChannelId));
+ params.appendWhere(Programs.COLUMN_CHANNEL_ID + "=?", channelId);
+ }
+ String paramStartTime = uri.getQueryParameter(TvContractCompat.PARAM_START_TIME);
+ String paramEndTime = uri.getQueryParameter(TvContractCompat.PARAM_END_TIME);
+ if (paramStartTime != null && paramEndTime != null) {
+ String startTime = String.valueOf(Long.parseLong(paramStartTime));
+ String endTime = String.valueOf(Long.parseLong(paramEndTime));
+ params.appendWhere(
+ Programs.COLUMN_START_TIME_UTC_MILLIS
+ + "<=? AND "
+ + Programs.COLUMN_END_TIME_UTC_MILLIS
+ + ">=? AND ?<=?",
+ endTime,
+ startTime,
+ startTime,
+ endTime);
+ }
+ break;
+ case MATCH_PROGRAM_ID:
+ params.setTables(PROGRAMS_TABLE);
+ params.appendWhere(Programs._ID + "=?", uri.getLastPathSegment());
+ break;
+ case MATCH_WATCHED_PROGRAM:
+ if (!callerHasAccessWatchedProgramsPermission()) {
+ throw new SecurityException("Access not allowed for " + uri);
+ }
+ params.setTables(WATCHED_PROGRAMS_TABLE);
+ params.appendWhere(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=?", "1");
+ break;
+ case MATCH_WATCHED_PROGRAM_ID:
+ if (!callerHasAccessWatchedProgramsPermission()) {
+ throw new SecurityException("Access not allowed for " + uri);
+ }
+ params.setTables(WATCHED_PROGRAMS_TABLE);
+ params.appendWhere(WatchedPrograms._ID + "=?", uri.getLastPathSegment());
+ params.appendWhere(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=?", "1");
+ break;
+ case MATCH_RECORDED_PROGRAM_ID:
+ params.appendWhere(RecordedPrograms._ID + "=?", uri.getLastPathSegment());
+ // fall-through
+ case MATCH_RECORDED_PROGRAM:
+ params.setTables(RECORDED_PROGRAMS_TABLE);
+ paramChannelId = uri.getQueryParameter(TvContractCompat.PARAM_CHANNEL);
+ if (paramChannelId != null) {
+ String channelId = String.valueOf(Long.parseLong(paramChannelId));
+ params.appendWhere(Programs.COLUMN_CHANNEL_ID + "=?", channelId);
+ }
+ break;
+ case MATCH_PREVIEW_PROGRAM_ID:
+ params.appendWhere(PreviewPrograms._ID + "=?", uri.getLastPathSegment());
+ // fall-through
+ case MATCH_PREVIEW_PROGRAM:
+ params.setTables(PREVIEW_PROGRAMS_TABLE);
+ paramChannelId = uri.getQueryParameter(TvContractCompat.PARAM_CHANNEL);
+ if (paramChannelId != null) {
+ String channelId = String.valueOf(Long.parseLong(paramChannelId));
+ params.appendWhere(PreviewPrograms.COLUMN_CHANNEL_ID + "=?", channelId);
+ }
+ break;
+ case MATCH_WATCH_NEXT_PROGRAM_ID:
+ params.appendWhere(WatchNextPrograms._ID + "=?", uri.getLastPathSegment());
+ // fall-through
+ case MATCH_WATCH_NEXT_PROGRAM:
+ params.setTables(WATCH_NEXT_PROGRAMS_TABLE);
+ break;
+ case MATCH_CHANNEL_ID_LOGO:
+ if (operation.equals(OP_DELETE)) {
+ params.setTables(CHANNELS_TABLE);
+ params.appendWhere(Channels._ID + "=?", uri.getPathSegments().get(1));
+ break;
+ }
+ // fall-through
+ case MATCH_PASSTHROUGH_ID:
+ throw new UnsupportedOperationException(operation + " not permmitted on " + uri);
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ return params;
+ }
+
+ private static String capitalize(String str) {
+ return Character.toUpperCase(str.charAt(0)) + str.substring(1);
+ }
+
+ @SuppressLint("DefaultLocale")
+ private void checkAndConvertGenre(ContentValues values) {
+ String canonicalGenres = values.getAsString(Programs.COLUMN_CANONICAL_GENRE);
+
+ if (!TextUtils.isEmpty(canonicalGenres)) {
+ // Check if the canonical genres are valid. If not, clear them.
+ String[] genres = Genres.decode(canonicalGenres);
+ for (String genre : genres) {
+ if (!Genres.isCanonical(genre)) {
+ values.putNull(Programs.COLUMN_CANONICAL_GENRE);
+ canonicalGenres = null;
+ break;
+ }
+ }
+ }
+
+ if (TextUtils.isEmpty(canonicalGenres)) {
+ // If the canonical genre is not set, try to map the broadcast genre to the canonical
+ // genre.
+ String broadcastGenres = values.getAsString(Programs.COLUMN_BROADCAST_GENRE);
+ if (!TextUtils.isEmpty(broadcastGenres)) {
+ Set<String> genreSet = new HashSet<>();
+ String[] genres = Genres.decode(broadcastGenres);
+ for (String genre : genres) {
+ String canonicalGenre = sGenreMap.get(genre.toUpperCase());
+ if (Genres.isCanonical(canonicalGenre)) {
+ genreSet.add(canonicalGenre);
+ }
+ }
+ if (genreSet.size() > 0) {
+ values.put(
+ Programs.COLUMN_CANONICAL_GENRE,
+ Genres.encode(genreSet.toArray(new String[genreSet.size()])));
+ }
+ }
+ }
+ }
+
+ private void checkAndConvertDeprecatedColumns(ContentValues values) {
+ if (values.containsKey(Programs.COLUMN_SEASON_NUMBER)) {
+ if (!values.containsKey(Programs.COLUMN_SEASON_DISPLAY_NUMBER)) {
+ values.put(
+ Programs.COLUMN_SEASON_DISPLAY_NUMBER,
+ values.getAsInteger(Programs.COLUMN_SEASON_NUMBER));
+ }
+ values.remove(Programs.COLUMN_SEASON_NUMBER);
+ }
+ if (values.containsKey(Programs.COLUMN_EPISODE_NUMBER)) {
+ if (!values.containsKey(Programs.COLUMN_EPISODE_DISPLAY_NUMBER)) {
+ values.put(
+ Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
+ values.getAsInteger(Programs.COLUMN_EPISODE_NUMBER));
+ }
+ values.remove(Programs.COLUMN_EPISODE_NUMBER);
+ }
+ }
+
+ // We might have more than one thread trying to make its way through applyBatch() so the
+ // notification coalescing needs to be thread-local to work correctly.
+ private final ThreadLocal<Set<Uri>> mTLBatchNotifications = new ThreadLocal<>();
+
+ private Set<Uri> getBatchNotificationsSet() {
+ return mTLBatchNotifications.get();
+ }
+
+ private void setBatchNotificationsSet(Set<Uri> batchNotifications) {
+ mTLBatchNotifications.set(batchNotifications);
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ setBatchNotificationsSet(new HashSet<Uri>());
+ Context context = getContext();
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ ContentProviderResult[] results = super.applyBatch(operations);
+ db.setTransactionSuccessful();
+ return results;
+ } finally {
+ db.endTransaction();
+ final Set<Uri> notifications = getBatchNotificationsSet();
+ setBatchNotificationsSet(null);
+ for (final Uri uri : notifications) {
+ context.getContentResolver().notifyChange(uri, null);
+ }
+ }
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ setBatchNotificationsSet(new HashSet<Uri>());
+ Context context = getContext();
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ int result = super.bulkInsert(uri, values);
+ db.setTransactionSuccessful();
+ return result;
+ } finally {
+ db.endTransaction();
+ final Set<Uri> notifications = getBatchNotificationsSet();
+ setBatchNotificationsSet(null);
+ for (final Uri notificationUri : notifications) {
+ context.getContentResolver().notifyChange(notificationUri, null);
+ }
+ }
+ }
+
+ private void notifyChange(Uri uri) {
+ final Set<Uri> batchNotifications = getBatchNotificationsSet();
+ if (batchNotifications != null) {
+ batchNotifications.add(uri);
+ } else {
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+ }
+
+ private boolean callerHasReadTvListingsPermission() {
+ return getContext().checkCallingOrSelfPermission(PERMISSION_READ_TV_LISTINGS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean callerHasAccessAllEpgDataPermission() {
+ return getContext().checkCallingOrSelfPermission(PERMISSION_ACCESS_ALL_EPG_DATA)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean callerHasAccessWatchedProgramsPermission() {
+ return getContext().checkCallingOrSelfPermission(PERMISSION_ACCESS_WATCHED_PROGRAMS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean callerHasModifyParentalControlsPermission() {
+ return getContext()
+ .checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void blockIllegalAccessToIdAndPackageName(Uri uri, ContentValues values) {
+ if (values.containsKey(BaseColumns._ID)) {
+ int match = sUriMatcher.match(uri);
+ switch (match) {
+ case MATCH_CHANNEL_ID:
+ case MATCH_PROGRAM_ID:
+ case MATCH_PREVIEW_PROGRAM_ID:
+ case MATCH_RECORDED_PROGRAM_ID:
+ case MATCH_WATCH_NEXT_PROGRAM_ID:
+ case MATCH_WATCHED_PROGRAM_ID:
+ if (TextUtils.equals(
+ values.getAsString(BaseColumns._ID), uri.getLastPathSegment())) {
+ break;
+ }
+ // fall through
+ default:
+ throw new IllegalArgumentException("Not allowed to change ID.");
+ }
+ }
+ if (values.containsKey(BaseTvColumns.COLUMN_PACKAGE_NAME)
+ && !callerHasAccessAllEpgDataPermission()
+ && !TextUtils.equals(
+ values.getAsString(BaseTvColumns.COLUMN_PACKAGE_NAME),
+ getCallingPackage_())) {
+ throw new SecurityException("Not allowed to change package name.");
+ }
+ }
+
+ private void blockIllegalAccessToChannelsSystemColumns(ContentValues values) {
+ if (values.containsKey(Channels.COLUMN_LOCKED)
+ && !callerHasModifyParentalControlsPermission()) {
+ throw new SecurityException("Not allowed to access Channels.COLUMN_LOCKED");
+ }
+ Boolean hasAccessAllEpgDataPermission = null;
+ if (values.containsKey(Channels.COLUMN_BROWSABLE)) {
+ hasAccessAllEpgDataPermission = callerHasAccessAllEpgDataPermission();
+ if (!hasAccessAllEpgDataPermission) {
+ throw new SecurityException("Not allowed to access Channels.COLUMN_BROWSABLE");
+ }
+ }
+ }
+
+ private void blockIllegalAccessToPreviewProgramsSystemColumns(ContentValues values) {
+ if (values.containsKey(PreviewPrograms.COLUMN_BROWSABLE)
+ && !callerHasAccessAllEpgDataPermission()) {
+ throw new SecurityException("Not allowed to access Programs.COLUMN_BROWSABLE");
+ }
+ }
+
+ private void blockIllegalAccessFromBlockedPackage() {
+ String callingPackageName = getCallingPackage_();
+ if (sBlockedPackages.containsKey(callingPackageName)) {
+ throw new SecurityException(
+ "Not allowed to access "
+ + TvContractCompat.AUTHORITY
+ + ", "
+ + callingPackageName
+ + " is blocked");
+ }
+ }
+
+ private boolean disallowModifyChannelType(ContentValues values, SqlParams params) {
+ if (values.containsKey(Channels.COLUMN_TYPE)) {
+ params.appendWhere(
+ Channels.COLUMN_TYPE + "=?", values.getAsString(Channels.COLUMN_TYPE));
+ return true;
+ }
+ return false;
+ }
+
+ private boolean disallowModifyChannelId(ContentValues values, SqlParams params) {
+ if (values.containsKey(PreviewPrograms.COLUMN_CHANNEL_ID)) {
+ params.appendWhere(
+ PreviewPrograms.COLUMN_CHANNEL_ID + "=?",
+ values.getAsString(PreviewPrograms.COLUMN_CHANNEL_ID));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ switch (sUriMatcher.match(uri)) {
+ case MATCH_CHANNEL_ID_LOGO:
+ return openLogoFile(uri, mode);
+ default:
+ throw new FileNotFoundException(uri.toString());
+ }
+ }
+
+ private ParcelFileDescriptor openLogoFile(Uri uri, String mode) throws FileNotFoundException {
+ long channelId = Long.parseLong(uri.getPathSegments().get(1));
+
+ SqlParams params =
+ new SqlParams(CHANNELS_TABLE, Channels._ID + "=?", String.valueOf(channelId));
+ if (!callerHasAccessAllEpgDataPermission()) {
+ if (callerHasReadTvListingsPermission()) {
+ params.appendWhere(
+ Channels.COLUMN_PACKAGE_NAME + "=? OR " + Channels.COLUMN_SEARCHABLE + "=?",
+ getCallingPackage_(),
+ "1");
+ } else {
+ params.appendWhere(Channels.COLUMN_PACKAGE_NAME + "=?", getCallingPackage_());
+ }
+ }
+
+ SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+ queryBuilder.setTables(params.getTables());
+
+ // We don't write the database here.
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ if (mode.equals("r")) {
+ String sql =
+ queryBuilder.buildQuery(
+ new String[] {CHANNELS_COLUMN_LOGO},
+ params.getSelection(),
+ null,
+ null,
+ null,
+ null);
+ ParcelFileDescriptor fd =
+ DatabaseUtils.blobFileDescriptorForQuery(db, sql, params.getSelectionArgs());
+ if (fd == null) {
+ throw new FileNotFoundException(uri.toString());
+ }
+ return fd;
+ } else {
+ try (Cursor cursor =
+ queryBuilder.query(
+ db,
+ new String[] {Channels._ID},
+ params.getSelection(),
+ params.getSelectionArgs(),
+ null,
+ null,
+ null)) {
+ if (cursor.getCount() < 1) {
+ // Fails early if corresponding channel does not exist.
+ // PipeMonitor may still fail to update DB later.
+ throw new FileNotFoundException(uri.toString());
+ }
+ }
+
+ try {
+ ParcelFileDescriptor[] pipeFds = ParcelFileDescriptor.createPipe();
+ PipeMonitor pipeMonitor = new PipeMonitor(pipeFds[0], channelId, params);
+ pipeMonitor.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ return pipeFds[1];
+ } catch (IOException ioe) {
+ FileNotFoundException fne = new FileNotFoundException(uri.toString());
+ fne.initCause(ioe);
+ throw fne;
+ }
+ }
+ }
+
+ /**
+ * Validates the sort order based on the given field set.
+ *
+ * @throws IllegalArgumentException if there is any unknown field.
+ */
+ @SuppressLint("DefaultLocale")
+ private static void validateSortOrder(String sortOrder, Set<String> possibleFields) {
+ if (TextUtils.isEmpty(sortOrder) || possibleFields.isEmpty()) {
+ return;
+ }
+ String[] orders = sortOrder.split(",");
+ for (String order : orders) {
+ String field =
+ order.replaceAll("\\s+", " ")
+ .trim()
+ .toLowerCase()
+ .replace(" asc", "")
+ .replace(" desc", "");
+ if (!possibleFields.contains(field)) {
+ throw new IllegalArgumentException("Illegal field in sort order " + order);
+ }
+ }
+ }
+
+ private class PipeMonitor extends AsyncTask<Void, Void, Void> {
+ private final ParcelFileDescriptor mPfd;
+ private final long mChannelId;
+ private final SqlParams mParams;
+
+ private PipeMonitor(ParcelFileDescriptor pfd, long channelId, SqlParams params) {
+ mPfd = pfd;
+ mChannelId = channelId;
+ mParams = params;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ int count = 0;
+ try (AutoCloseInputStream is = new AutoCloseInputStream(mPfd);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ Bitmap bitmap = BitmapFactory.decodeStream(is);
+ if (bitmap == null) {
+ Log.e(TAG, "Failed to decode logo image for channel ID " + mChannelId);
+ return null;
+ }
+
+ float scaleFactor =
+ Math.min(
+ 1f,
+ ((float) MAX_LOGO_IMAGE_SIZE)
+ / Math.max(bitmap.getWidth(), bitmap.getHeight()));
+ if (scaleFactor < 1f) {
+ bitmap =
+ Bitmap.createScaledBitmap(
+ bitmap,
+ (int) (bitmap.getWidth() * scaleFactor),
+ (int) (bitmap.getHeight() * scaleFactor),
+ false);
+ }
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+ byte[] bytes = baos.toByteArray();
+
+ ContentValues values = new ContentValues();
+ values.put(CHANNELS_COLUMN_LOGO, bytes);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ count =
+ db.update(
+ mParams.getTables(),
+ values,
+ mParams.getSelection(),
+ mParams.getSelectionArgs());
+ if (count > 0) {
+ Uri uri = TvContractCompat.buildChannelLogoUri(mChannelId);
+ notifyChange(uri);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write logo for channel ID " + mChannelId, e);
+
+ } finally {
+ if (count == 0) {
+ try {
+ mPfd.closeWithError("Failed to write logo for channel ID " + mChannelId);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Failed to close pipe", ioe);
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Column definitions for the TV programs that the user watched. Applications do not have access
+ * to this table.
+ *
+ * <p>
+ *
+ * <p>By default, the query results will be sorted by {@link
+ * WatchedPrograms#COLUMN_WATCH_START_TIME_UTC_MILLIS} in descending order.
+ *
+ * @hide
+ */
+ public static final class WatchedPrograms implements BaseTvColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + TvContract.AUTHORITY + "/watched_program");
+
+ /** The MIME type of a directory of watched programs. */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/watched_program";
+
+ /** The MIME type of a single item in this table. */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/watched_program";
+
+ /**
+ * The UTC time that the user started watching this TV program, in milliseconds since the
+ * epoch.
+ *
+ * <p>
+ *
+ * <p>Type: INTEGER (long)
+ */
+ public static final String COLUMN_WATCH_START_TIME_UTC_MILLIS =
+ "watch_start_time_utc_millis";
+
+ /**
+ * The UTC time that the user stopped watching this TV program, in milliseconds since the
+ * epoch.
+ *
+ * <p>
+ *
+ * <p>Type: INTEGER (long)
+ */
+ public static final String COLUMN_WATCH_END_TIME_UTC_MILLIS = "watch_end_time_utc_millis";
+
+ /**
+ * The ID of the TV channel that provides this TV program.
+ *
+ * <p>
+ *
+ * <p>This is a required field.
+ *
+ * <p>
+ *
+ * <p>Type: INTEGER (long)
+ */
+ public static final String COLUMN_CHANNEL_ID = "channel_id";
+
+ /**
+ * The title of this TV program.
+ *
+ * <p>
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_TITLE = "title";
+
+ /**
+ * The start time of this TV program, in milliseconds since the epoch.
+ *
+ * <p>
+ *
+ * <p>Type: INTEGER (long)
+ */
+ public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
+
+ /**
+ * The end time of this TV program, in milliseconds since the epoch.
+ *
+ * <p>
+ *
+ * <p>Type: INTEGER (long)
+ */
+ public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
+
+ /**
+ * The description of this TV program.
+ *
+ * <p>
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_DESCRIPTION = "description";
+
+ /**
+ * Extra parameters given to {@link TvInputService.Session#tune(Uri, android.os.Bundle)
+ * TvInputService.Session.tune(Uri, android.os.Bundle)} when tuning to the channel that
+ * provides this TV program. (Used internally.)
+ *
+ * <p>
+ *
+ * <p>This column contains an encoded string that represents comma-separated key-value pairs
+ * of the tune parameters. (Ex. "[key1]=[value1], [key2]=[value2]"). '%' is used as an
+ * escape character for '%', '=', and ','.
+ *
+ * <p>
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_INTERNAL_TUNE_PARAMS = "tune_params";
+
+ /**
+ * The session token of this TV program. (Used internally.)
+ *
+ * <p>
+ *
+ * <p>This contains a String representation of {@link IBinder} for {@link
+ * TvInputService.Session} that provides the current TV program. It is used internally to
+ * distinguish watched programs entries from different TV input sessions.
+ *
+ * <p>
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_INTERNAL_SESSION_TOKEN = "session_token";
+
+ private WatchedPrograms() {}
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/SingletonProvider.java b/tests/common/src/com/android/tv/testing/SingletonProvider.java
new file mode 100644
index 00000000..d9c2d409
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/SingletonProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import javax.inject.Provider;
+
+/** A Provider that always returns the same instance. */
+public class SingletonProvider<T> implements Provider<T> {
+ private final T t;
+
+ private SingletonProvider(T t) {
+ this.t = t;
+ }
+
+ @Override
+ public T get() {
+ return t;
+ }
+
+ public static <S, T extends S> Provider<S> create(T t) {
+ return new SingletonProvider<S>(t);
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/TestSingletonApp.java b/tests/common/src/com/android/tv/testing/TestSingletonApp.java
new file mode 100644
index 00000000..f55ed8d4
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/TestSingletonApp.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2017 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.testing;
+
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.tv.TvInputManager;
+import android.os.AsyncTask;
+import com.android.tv.InputSessionManager;
+import com.android.tv.MainActivityWrapper;
+import com.android.tv.TvSingletons;
+import com.android.tv.analytics.Analytics;
+import com.android.tv.analytics.Tracker;
+import com.android.tv.common.BaseApplication;
+import com.android.tv.common.config.api.RemoteConfig;
+import com.android.tv.common.experiments.ExperimentLoader;
+import com.android.tv.common.recording.RecordingStorageStatusManager;
+import com.android.tv.common.util.Clock;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.PreviewDataManager;
+import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.epg.EpgFetcher;
+import com.android.tv.data.epg.EpgReader;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.DvrScheduleManager;
+import com.android.tv.dvr.DvrWatchedPositionManager;
+import com.android.tv.dvr.recorder.RecordingScheduler;
+import com.android.tv.perf.PerformanceMonitor;
+import com.android.tv.perf.StubPerformanceMonitor;
+import com.android.tv.testing.dvr.DvrDataManagerInMemoryImpl;
+import com.android.tv.testing.testdata.TestData;
+import com.android.tv.tuner.TunerInputController;
+import com.android.tv.util.SetupUtils;
+import com.android.tv.util.TvInputManagerHelper;
+import com.android.tv.util.account.AccountHelper;
+import java.util.concurrent.Executor;
+import javax.inject.Provider;
+
+/** Test application for Live TV. */
+public class TestSingletonApp extends Application implements TvSingletons {
+ public final FakeClock fakeClock = FakeClock.createWithCurrentTime();
+ public final FakeEpgReader epgReader = new FakeEpgReader(fakeClock);
+ public final FakeRemoteConfig remoteConfig = new FakeRemoteConfig();
+ public final FakeEpgFetcher epgFetcher = new FakeEpgFetcher();
+
+ public FakeTvInputManagerHelper tvInputManagerHelper;
+ public SetupUtils setupUtils;
+ public DvrManager dvrManager;
+ public DvrDataManager mDvrDataManager;
+
+ private final Provider<EpgReader> mEpgReaderProvider = SingletonProvider.create(epgReader);
+ private TunerInputController mTunerInputController;
+ private PerformanceMonitor mPerformanceMonitor;
+ private ChannelDataManager mChannelDataManager;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mTunerInputController =
+ new TunerInputController(
+ ComponentName.unflattenFromString(getEmbeddedTunerInputId()));
+
+ tvInputManagerHelper = new FakeTvInputManagerHelper(this);
+ setupUtils = SetupUtils.createForTvSingletons(this);
+ tvInputManagerHelper.start();
+ mChannelDataManager = new ChannelDataManager(this, tvInputManagerHelper);
+ mChannelDataManager.start();
+ mDvrDataManager = new DvrDataManagerInMemoryImpl(this, fakeClock);
+ // HACK reset the singleton for tests
+ BaseApplication.sSingletons = this;
+ }
+
+ public void loadTestData(TestData testData, long durationMs) {
+ tvInputManagerHelper
+ .getFakeTvInputManager()
+ .add(testData.getTvInputInfo(), TvInputManager.INPUT_STATE_CONNECTED);
+ testData.init(this, fakeClock, durationMs);
+ }
+
+ @Override
+ public Analytics getAnalytics() {
+ return null;
+ }
+
+ @Override
+ public void handleInputCountChanged() {}
+
+ @Override
+ public ChannelDataManager getChannelDataManager() {
+ return mChannelDataManager;
+ }
+
+ @Override
+ public boolean isChannelDataManagerLoadFinished() {
+ return false;
+ }
+
+ @Override
+ public ProgramDataManager getProgramDataManager() {
+ return null;
+ }
+
+ @Override
+ public boolean isProgramDataManagerCurrentProgramsLoadFinished() {
+ return false;
+ }
+
+ @Override
+ public PreviewDataManager getPreviewDataManager() {
+ return null;
+ }
+
+ @Override
+ public DvrDataManager getDvrDataManager() {
+ return mDvrDataManager;
+ }
+
+ @Override
+ public DvrScheduleManager getDvrScheduleManager() {
+ return null;
+ }
+
+ @Override
+ public DvrManager getDvrManager() {
+ return dvrManager;
+ }
+
+ @Override
+ public RecordingScheduler getRecordingScheduler() {
+ return null;
+ }
+
+ @Override
+ public DvrWatchedPositionManager getDvrWatchedPositionManager() {
+ return null;
+ }
+
+ @Override
+ public InputSessionManager getInputSessionManager() {
+ return null;
+ }
+
+ @Override
+ public Tracker getTracker() {
+ return null;
+ }
+
+ @Override
+ public TvInputManagerHelper getTvInputManagerHelper() {
+ return tvInputManagerHelper;
+ }
+
+ @Override
+ public Provider<EpgReader> providesEpgReader() {
+ return mEpgReaderProvider;
+ }
+
+ @Override
+ public EpgFetcher getEpgFetcher() {
+ return epgFetcher;
+ }
+
+ @Override
+ public SetupUtils getSetupUtils() {
+ return setupUtils;
+ }
+
+ @Override
+ public TunerInputController getTunerInputController() {
+ return mTunerInputController;
+ }
+
+ @Override
+ public ExperimentLoader getExperimentLoader() {
+ return new ExperimentLoader();
+ }
+
+ @Override
+ public MainActivityWrapper getMainActivityWrapper() {
+ return null;
+ }
+
+ @Override
+ public AccountHelper getAccountHelper() {
+ return null;
+ }
+
+ @Override
+ public Clock getClock() {
+ return fakeClock;
+ }
+
+ @Override
+ public RecordingStorageStatusManager getRecordingStorageStatusManager() {
+ return null;
+ }
+
+ @Override
+ public RemoteConfig getRemoteConfig() {
+ return remoteConfig;
+ }
+
+ @Override
+ public Intent getTunerSetupIntent(Context context) {
+ return null;
+ }
+
+ @Override
+ public boolean isRunningInMainProcess() {
+ return false;
+ }
+
+ @Override
+ public PerformanceMonitor getPerformanceMonitor() {
+ if (mPerformanceMonitor == null) {
+ mPerformanceMonitor = new StubPerformanceMonitor();
+ }
+ return mPerformanceMonitor;
+ }
+
+ @Override
+ public String getEmbeddedTunerInputId() {
+ return "com.android.tv/.tuner.tvinput.TunerTvInputService";
+ }
+
+ @Override
+ public Executor getDbExecutor() {
+ return AsyncTask.SERIAL_EXECUTOR;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/activities/BaseMainActivityTestCase.java b/tests/common/src/com/android/tv/testing/activities/BaseMainActivityTestCase.java
new file mode 100644
index 00000000..666f8181
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/activities/BaseMainActivityTestCase.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 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.testing.activities;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.support.test.rule.ActivityTestRule;
+import android.text.TextUtils;
+import com.android.tv.MainActivity;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
+import com.android.tv.testing.data.ChannelInfo;
+import com.android.tv.testing.testinput.ChannelStateData;
+import com.android.tv.testing.testinput.TestInputControlConnection;
+import com.android.tv.testing.testinput.TestInputControlUtils;
+import com.android.tv.testing.testinput.TvTestInputConstants;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+
+/** Base TestCase for tests that need a {@link MainActivity}. */
+public abstract class BaseMainActivityTestCase {
+ private static final String TAG = "BaseMainActivityTest";
+ private static final int CHANNEL_LOADING_CHECK_INTERVAL_MS = 10;
+
+ @Rule
+ public ActivityTestRule<MainActivity> mActivityTestRule =
+ new ActivityTestRule<>(MainActivity.class);
+
+ protected final TestInputControlConnection mConnection = new TestInputControlConnection();
+
+ protected MainActivity mActivity;
+
+ @Before
+ public void setUp() {
+ mActivity = mActivityTestRule.getActivity();
+ // TODO: ensure the SampleInputs are setup.
+ getInstrumentation()
+ .getTargetContext()
+ .bindService(
+ TestInputControlUtils.createIntent(),
+ mConnection,
+ Context.BIND_AUTO_CREATE);
+ }
+
+ @Before
+ public void tearDown() {
+ if (mConnection.isBound()) {
+ getInstrumentation().getTargetContext().unbindService(mConnection);
+ }
+ }
+
+ /**
+ * Tune to {@code channel}.
+ *
+ * @param channel the channel to tune to.
+ */
+ protected void tuneToChannel(final Channel channel) {
+ // Run on UI thread so views can be modified
+ getInstrumentation()
+ .runOnMainSync(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivity.tuneToChannel(channel);
+ }
+ });
+ }
+
+ /** Sleep until @{@link ChannelDataManager#isDbLoadFinished()} is true. */
+ protected void waitUntilChannelLoadingFinish() {
+ ChannelDataManager channelDataManager = mActivity.getChannelDataManager();
+ while (!channelDataManager.isDbLoadFinished()) {
+ getInstrumentation().waitForIdleSync();
+ SystemClock.sleep(CHANNEL_LOADING_CHECK_INTERVAL_MS);
+ }
+ }
+
+ /**
+ * Tune to the channel with {@code name}.
+ *
+ * @param name the name of the channel to find.
+ */
+ protected void tuneToChannel(String name) {
+ Channel c = findChannelWithName(name);
+ tuneToChannel(c);
+ }
+
+ /** Tune to channel. */
+ protected void tuneToChannel(ChannelInfo channel) {
+ tuneToChannel(channel.name);
+ }
+
+ /**
+ * Update the channel state to {@code data} then tune to that channel.
+ *
+ * @param data the state to update the channel with.
+ * @param channel the channel to tune to
+ */
+ protected void updateThenTune(ChannelStateData data, ChannelInfo channel) {
+ if (channel.equals(TvTestInputConstants.CH_1_DEFAULT_DONT_MODIFY)) {
+ throw new IllegalArgumentException(
+ "By convention "
+ + TvTestInputConstants.CH_1_DEFAULT_DONT_MODIFY.name
+ + " should not be modified.");
+ }
+ mConnection.updateChannelState(channel, data);
+ tuneToChannel(channel);
+ }
+
+ private Channel findChannelWithName(String displayName) {
+ waitUntilChannelLoadingFinish();
+ Channel channel = null;
+ List<Channel> channelList = mActivity.getChannelDataManager().getChannelList();
+ for (Channel c : channelList) {
+ if (TextUtils.equals(c.getDisplayName(), displayName)) {
+ channel = c;
+ break;
+ }
+ }
+ if (channel == null) {
+ throw new AssertionError("'" + displayName + "' channel not found");
+ }
+ return channel;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/constants/ConfigConstants.java b/tests/common/src/com/android/tv/testing/constants/ConfigConstants.java
new file mode 100644
index 00000000..890c51e0
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/constants/ConfigConstants.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 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.testing.constants;
+
+import android.os.Build;
+
+/** Constants for Robolectic Config. */
+public final class ConfigConstants {
+
+ public static final String MANIFEST = "vendor/unbundled_google/packages/TV/AndroidManifest.xml";
+ public static final int SDK = Build.VERSION_CODES.M;
+
+ private ConfigConstants() {}
+}
diff --git a/tests/common/src/com/android/tv/testing/constants/Constants.java b/tests/common/src/com/android/tv/testing/constants/Constants.java
new file mode 100644
index 00000000..09e1ada1
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/constants/Constants.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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.testing.constants;
+
+import android.media.tv.TvTrackInfo;
+
+/** Constants for testing. */
+public final class Constants {
+ public static final int FUNC_TEST_CHANNEL_COUNT = 100;
+ public static final int UNIT_TEST_CHANNEL_COUNT = 4;
+ public static final int JANK_TEST_CHANNEL_COUNT = 500; // TODO: increase to 1000 see b/23526997
+
+ public static final TvTrackInfo EN_STEREO_AUDIO_TRACK =
+ new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "English Stereo Audio")
+ .setLanguage("en")
+ .setAudioChannelCount(2)
+ .build();
+ public static final TvTrackInfo GENERIC_AUDIO_TRACK =
+ new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "Generic Audio").build();
+
+ public static final TvTrackInfo FHD1080P50_VIDEO_TRACK =
+ new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "FHD Video")
+ .setVideoHeight(1080)
+ .setVideoWidth(1920)
+ .setVideoFrameRate(50)
+ .build();
+ public static final TvTrackInfo SVGA_VIDEO_TRACK =
+ new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "SVGA Video")
+ .setVideoHeight(600)
+ .setVideoWidth(800)
+ .build();
+
+ private Constants() {}
+}
diff --git a/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java b/tests/common/src/com/android/tv/testing/constants/TvContentRatingConstants.java
index c4c96fed..e1a3d906 100644
--- a/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java
+++ b/tests/common/src/com/android/tv/testing/constants/TvContentRatingConstants.java
@@ -14,20 +14,21 @@
* limitations under the License.
*/
-package com.android.tv.testing;
+package com.android.tv.testing.constants;
import android.media.tv.TvContentRating;
-/**
- * Constants for the content rating strings.
- */
+/** Constants for the content rating strings. */
public final class TvContentRatingConstants {
/**
* A content rating object.
*
* <p>Domain: com.android.tv
+ *
* <p>Rating system: US_TV
+ *
* <p>Rating: US_TV_Y7
+ *
* <p>Sub ratings: US_TV_FV
*/
public static final TvContentRating CONTENT_RATING_US_TV_Y7_US_TV_FV =
@@ -39,7 +40,9 @@ public final class TvContentRatingConstants {
* A content rating object.
*
* <p>Domain: com.android.tv
+ *
* <p>Rating system: US_TV
+ *
* <p>Rating: US_TV_MA
*/
public static final TvContentRating CONTENT_RATING_US_TV_MA =
@@ -51,11 +54,14 @@ public final class TvContentRatingConstants {
* A content rating object.
*
* <p>Domain: com.android.tv
+ *
* <p>Rating system: US_TV
+ *
* <p>Rating: US_TV_PG
+ *
* <p>Sub ratings: US_TV_L, US_TV_S
*/
public static final TvContentRating CONTENT_RATING_US_TV_PG_US_TV_L_US_TV_S =
- TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_PG", "US_TV_L",
- "US_TV_S");
+ TvContentRating.createRating(
+ "com.android.tv", "US_TV", "US_TV_PG", "US_TV_L", "US_TV_S");
}
diff --git a/tests/common/src/com/android/tv/testing/ChannelInfo.java b/tests/common/src/com/android/tv/testing/data/ChannelInfo.java
index 946c0b55..e39c057d 100644
--- a/tests/common/src/com/android/tv/testing/ChannelInfo.java
+++ b/tests/common/src/com/android/tv/testing/data/ChannelInfo.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.testing;
+package com.android.tv.testing.data;
import android.content.ContentResolver;
import android.content.Context;
@@ -23,14 +23,12 @@ import android.media.tv.TvContract;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.SparseArray;
-
import java.util.Objects;
-/**
- * Channel Information.
- */
+/** Channel Information. */
public final class ChannelInfo {
private static final SparseArray<String> VIDEO_HEIGHT_TO_FORMAT_MAP = new SparseArray<>();
+
static {
VIDEO_HEIGHT_TO_FORMAT_MAP.put(480, TvContract.Channels.VIDEO_FORMAT_480P);
VIDEO_HEIGHT_TO_FORMAT_MAP.put(576, TvContract.Channels.VIDEO_FORMAT_576P);
@@ -41,9 +39,9 @@ public final class ChannelInfo {
}
public static final String[] PROJECTION = {
- TvContract.Channels.COLUMN_DISPLAY_NUMBER,
- TvContract.Channels.COLUMN_DISPLAY_NAME,
- TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID,
+ TvContract.Channels.COLUMN_DISPLAY_NUMBER,
+ TvContract.Channels.COLUMN_DISPLAY_NAME,
+ TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID,
};
public final String number;
@@ -67,14 +65,15 @@ public final class ChannelInfo {
* Create a channel info for TVTestInput.
*
* @param context a context to insert logo. It can be null if logo isn't needed.
- * @param channelNumber a channel number to be use as an identifier.
- * {@link #originalNetworkId} will be assigned the same value, too.
+ * @param channelNumber a channel number to be use as an identifier. {@link #originalNetworkId}
+ * will be assigned the same value, too.
*/
public static ChannelInfo create(@Nullable Context context, int channelNumber) {
- Builder builder = new Builder()
- .setNumber(String.valueOf(channelNumber))
- .setName("Channel " + channelNumber)
- .setOriginalNetworkId(channelNumber);
+ Builder builder =
+ new Builder()
+ .setNumber(String.valueOf(channelNumber))
+ .setName("Channel " + channelNumber)
+ .setOriginalNetworkId(channelNumber);
if (context != null) {
// tests/input/tools/get_test_logos.sh only stores 1000 logos.
builder.setLogoUrl(getUriStringForChannelLogo(context, channelNumber));
@@ -88,7 +87,9 @@ public final class ChannelInfo {
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(context.getPackageName())
.path("drawable")
- .appendPath("ch_" + index + "_logo").build().toString();
+ .appendPath("ch_" + index + "_logo")
+ .build()
+ .toString();
}
public static ChannelInfo fromCursor(Cursor c) {
@@ -109,10 +110,22 @@ public final class ChannelInfo {
return builder.build();
}
- private ChannelInfo(String number, String name, String logoUrl, int originalNetworkId,
- int videoWidth, int videoHeight, float videoPixelAspectRatio, int audioChannel,
- int audioLanguageCount, boolean hasClosedCaption, ProgramInfo program,
- String appLinkText, int appLinkColor, String appLinkIconUri, String appLinkPosterArtUri,
+ private ChannelInfo(
+ String number,
+ String name,
+ String logoUrl,
+ int originalNetworkId,
+ int videoWidth,
+ int videoHeight,
+ float videoPixelAspectRatio,
+ int audioChannel,
+ int audioLanguageCount,
+ boolean hasClosedCaption,
+ ProgramInfo program,
+ String appLinkText,
+ int appLinkColor,
+ String appLinkIconUri,
+ String appLinkPosterArtUri,
String appLinkIntentUri) {
this.number = number;
this.name = name;
@@ -139,20 +152,35 @@ public final class ChannelInfo {
@Override
public String toString() {
return "Channel{"
- + "number=" + number
- + ", name=" + name
- + ", logoUri=" + logoUrl
- + ", originalNetworkId=" + originalNetworkId
- + ", videoWidth=" + videoWidth
- + ", videoHeight=" + videoHeight
- + ", audioChannel=" + audioChannel
- + ", audioLanguageCount=" + audioLanguageCount
- + ", hasClosedCaption=" + hasClosedCaption
- + ", appLinkText=" + appLinkText
- + ", appLinkColor=" + appLinkColor
- + ", appLinkIconUri=" + appLinkIconUri
- + ", appLinkPosterArtUri=" + appLinkPosterArtUri
- + ", appLinkIntentUri=" + appLinkIntentUri + "}";
+ + "number="
+ + number
+ + ", name="
+ + name
+ + ", logoUri="
+ + logoUrl
+ + ", originalNetworkId="
+ + originalNetworkId
+ + ", videoWidth="
+ + videoWidth
+ + ", videoHeight="
+ + videoHeight
+ + ", audioChannel="
+ + audioChannel
+ + ", audioLanguageCount="
+ + audioLanguageCount
+ + ", hasClosedCaption="
+ + hasClosedCaption
+ + ", appLinkText="
+ + appLinkText
+ + ", appLinkColor="
+ + appLinkColor
+ + ", appLinkIconUri="
+ + appLinkIconUri
+ + ", appLinkPosterArtUri="
+ + appLinkPosterArtUri
+ + ", appLinkIntentUri="
+ + appLinkIntentUri
+ + "}";
}
@Override
@@ -164,21 +192,21 @@ public final class ChannelInfo {
return false;
}
ChannelInfo that = (ChannelInfo) o;
- return Objects.equals(originalNetworkId, that.originalNetworkId) &&
- Objects.equals(videoWidth, that.videoWidth) &&
- Objects.equals(videoHeight, that.videoHeight) &&
- Objects.equals(audioChannel, that.audioChannel) &&
- Objects.equals(audioLanguageCount, that.audioLanguageCount) &&
- Objects.equals(hasClosedCaption, that.hasClosedCaption) &&
- Objects.equals(appLinkColor, that.appLinkColor) &&
- Objects.equals(number, that.number) &&
- Objects.equals(name, that.name) &&
- Objects.equals(logoUrl, that.logoUrl) &&
- Objects.equals(program, that.program) &&
- Objects.equals(appLinkText, that.appLinkText) &&
- Objects.equals(appLinkIconUri, that.appLinkIconUri) &&
- Objects.equals(appLinkPosterArtUri, that.appLinkPosterArtUri) &&
- Objects.equals(appLinkIntentUri, that.appLinkIntentUri);
+ return Objects.equals(originalNetworkId, that.originalNetworkId)
+ && Objects.equals(videoWidth, that.videoWidth)
+ && Objects.equals(videoHeight, that.videoHeight)
+ && Objects.equals(audioChannel, that.audioChannel)
+ && Objects.equals(audioLanguageCount, that.audioLanguageCount)
+ && Objects.equals(hasClosedCaption, that.hasClosedCaption)
+ && Objects.equals(appLinkColor, that.appLinkColor)
+ && Objects.equals(number, that.number)
+ && Objects.equals(name, that.name)
+ && Objects.equals(logoUrl, that.logoUrl)
+ && Objects.equals(program, that.program)
+ && Objects.equals(appLinkText, that.appLinkText)
+ && Objects.equals(appLinkIconUri, that.appLinkIconUri)
+ && Objects.equals(appLinkPosterArtUri, that.appLinkPosterArtUri)
+ && Objects.equals(appLinkIntentUri, that.appLinkIntentUri);
}
@Override
@@ -186,17 +214,15 @@ public final class ChannelInfo {
return Objects.hash(number, name, originalNetworkId);
}
- /**
- * Builder class for {@code ChannelInfo}.
- */
+ /** Builder class for {@code ChannelInfo}. */
public static class Builder {
private String mNumber;
private String mName;
private String mLogoUrl = null;
private int mOriginalNetworkId;
- private int mVideoWidth = 1920; // Width for HD video.
- private int mVideoHeight = 1080; // Height for HD video.
- private float mVideoPixelAspectRatio = 1.0f; //default value
+ private int mVideoWidth = 1920; // Width for HD video.
+ private int mVideoHeight = 1080; // Height for HD video.
+ private float mVideoPixelAspectRatio = 1.0f; // default value
private int mAudioChannel;
private int mAudioLanguageCount;
private boolean mHasClosedCaption;
@@ -207,8 +233,7 @@ public final class ChannelInfo {
private String mAppLinkPosterArtUri;
private String mAppLinkIntentUri;
- public Builder() {
- }
+ public Builder() {}
public Builder(ChannelInfo other) {
mNumber = other.number;
@@ -305,11 +330,23 @@ public final class ChannelInfo {
}
public ChannelInfo build() {
- return new ChannelInfo(mNumber, mName, mLogoUrl, mOriginalNetworkId,
- mVideoWidth, mVideoHeight, mVideoPixelAspectRatio, mAudioChannel,
- mAudioLanguageCount, mHasClosedCaption, mProgram, mAppLinkText, mAppLinkColor,
- mAppLinkIconUri, mAppLinkPosterArtUri, mAppLinkIntentUri);
-
+ return new ChannelInfo(
+ mNumber,
+ mName,
+ mLogoUrl,
+ mOriginalNetworkId,
+ mVideoWidth,
+ mVideoHeight,
+ mVideoPixelAspectRatio,
+ mAudioChannel,
+ mAudioLanguageCount,
+ mHasClosedCaption,
+ mProgram,
+ mAppLinkText,
+ mAppLinkColor,
+ mAppLinkIconUri,
+ mAppLinkPosterArtUri,
+ mAppLinkIntentUri);
}
}
}
diff --git a/tests/common/src/com/android/tv/testing/ChannelUtils.java b/tests/common/src/com/android/tv/testing/data/ChannelUtils.java
index bfb766d6..920c7087 100644
--- a/tests/common/src/com/android/tv/testing/ChannelUtils.java
+++ b/tests/common/src/com/android/tv/testing/data/ChannelUtils.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tv.testing;
+package com.android.tv.testing.data;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -27,24 +27,22 @@ import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
-
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-/**
- * Static helper methods for working with {@link android.media.tv.TvContract}.
- */
+/** Static helper methods for working with {@link android.media.tv.TvContract}. */
public class ChannelUtils {
private static final String TAG = "ChannelUtils";
private static final boolean DEBUG = false;
/**
- * Query and return the map of (channel_id, ChannelInfo).
- * See: {@link ChannelInfo#fromCursor(Cursor)}.
+ * Query and return the map of (channel_id, ChannelInfo). See: {@link
+ * com.android.tv.testing.data.ChannelInfo#fromCursor(Cursor)}.
*/
@WorkerThread
public static Map<Long, ChannelInfo> queryChannelInfoMapForTvInput(
@@ -55,8 +53,8 @@ public class ChannelUtils {
String[] projections = new String[ChannelInfo.PROJECTION.length + 1];
projections[0] = Channels._ID;
System.arraycopy(ChannelInfo.PROJECTION, 0, projections, 1, ChannelInfo.PROJECTION.length);
- try (Cursor cursor = context.getContentResolver()
- .query(uri, projections, null, null, null)) {
+ try (Cursor cursor =
+ context.getContentResolver().query(uri, projections, null, null, null)) {
if (cursor != null) {
while (cursor.moveToNext()) {
map.put(cursor.getLong(0), ChannelInfo.fromCursor(cursor));
@@ -113,10 +111,14 @@ public class ChannelUtils {
Long rowId = existingChannelsMap.get(channel.originalNetworkId);
Uri uri;
if (rowId == null) {
- if (DEBUG) Log.d(TAG, "Inserting "+ channel);
+ if (DEBUG) {
+ Log.d(TAG, "Inserting " + channel);
+ }
uri = resolver.insert(TvContract.Channels.CONTENT_URI, values);
} else {
- if (DEBUG) Log.d(TAG, "Updating "+ channel);
+ if (DEBUG) {
+ Log.d(TAG, "Updating " + channel);
+ }
uri = TvContract.buildChannelUri(rowId);
resolver.update(uri, values, null, null);
existingChannelsMap.remove(channel.originalNetworkId);
@@ -149,6 +151,14 @@ public class ChannelUtils {
// Prevent instantiation.
}
+ public static List<ChannelInfo> createChannelInfos(Context context, int channelCount) {
+ List<ChannelInfo> channels = new ArrayList<>();
+ for (int i = 1; i <= channelCount; i++) {
+ channels.add(ChannelInfo.create(context, i));
+ }
+ return channels;
+ }
+
public static class InsertLogosTask extends AsyncTask<Map<Uri, String>, Void, Void> {
private final Context mContext;
diff --git a/tests/common/src/com/android/tv/testing/ProgramInfo.java b/tests/common/src/com/android/tv/testing/data/ProgramInfo.java
index b1aaea6b..6d801425 100644
--- a/tests/common/src/com/android/tv/testing/ProgramInfo.java
+++ b/tests/common/src/com/android/tv/testing/data/ProgramInfo.java
@@ -14,80 +14,83 @@
* limitations under the License.
*/
-package com.android.tv.testing;
+package com.android.tv.testing.data;
import android.content.Context;
import android.database.Cursor;
import android.media.tv.TvContentRating;
import android.media.tv.TvContract;
-
+import com.android.tv.testing.R;
+import com.android.tv.testing.utils.Utils;
+import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public final class ProgramInfo {
- /**
- * If this is specify for title, it will be generated by adding index.
- */
+ /** If this is specify for title, it will be generated by adding index. */
public static final String GEN_TITLE = "";
/**
- * If this is specify for episode title, it will be generated by adding index.
- * Also, season and episode numbers would be generated, too.
- * see: {@link #build} for detail.
+ * If this is specify for episode title, it will be generated by adding index. Also, season and
+ * episode numbers would be generated, too. see: {@link #build} for detail.
*/
public static final String GEN_EPISODE = "";
+
private static final int SEASON_MAX = 10;
private static final int EPISODE_MAX = 12;
/**
- * If this is specify for poster art,
- * it will be selected one of {@link #POSTER_ARTS_RES} in order.
+ * If this is specify for poster art, it will be selected one of {@link #POSTER_ARTS_RES} in
+ * order.
*/
public static final String GEN_POSTER = "GEN";
+
private static final int[] POSTER_ARTS_RES = {
- 0,
- R.drawable.blue,
- R.drawable.red_large,
- R.drawable.green,
- R.drawable.red,
- R.drawable.green_large,
- R.drawable.blue_small};
+ 0,
+ R.drawable.blue,
+ R.drawable.red_large,
+ R.drawable.green,
+ R.drawable.red,
+ R.drawable.green_large,
+ R.drawable.blue_small
+ };
/**
- * If this is specified for duration,
- * it will be selected one of {@link #DURATIONS_MS} in order.
+ * If this is specified for duration, it will be selected one of {@link #DURATIONS_MS} in order.
*/
public static final int GEN_DURATION = -1;
+
private static final long[] DURATIONS_MS = {
- TimeUnit.MINUTES.toMillis(15),
- TimeUnit.MINUTES.toMillis(45),
- TimeUnit.MINUTES.toMillis(90),
- TimeUnit.MINUTES.toMillis(60),
- TimeUnit.MINUTES.toMillis(30),
- TimeUnit.MINUTES.toMillis(45),
- TimeUnit.MINUTES.toMillis(60),
- TimeUnit.MINUTES.toMillis(90),
- TimeUnit.HOURS.toMillis(5)};
- private static long DURATIONS_SUM_MS;
+ TimeUnit.MINUTES.toMillis(15),
+ TimeUnit.MINUTES.toMillis(45),
+ TimeUnit.MINUTES.toMillis(90),
+ TimeUnit.MINUTES.toMillis(60),
+ TimeUnit.MINUTES.toMillis(30),
+ TimeUnit.MINUTES.toMillis(45),
+ TimeUnit.MINUTES.toMillis(60),
+ TimeUnit.MINUTES.toMillis(90),
+ TimeUnit.HOURS.toMillis(5)
+ };
+ private static long durationsSumMs;
+
static {
- DURATIONS_SUM_MS = 0;
+ durationsSumMs = 0;
for (long duration : DURATIONS_MS) {
- DURATIONS_SUM_MS += duration;
+ durationsSumMs += duration;
}
}
- /**
- * If this is specified for genre,
- * it will be selected one of {@link #GENRES} in order.
- */
+ /** If this is specified for genre, it will be selected one of {@link #GENRES} in order. */
public static final String GEN_GENRE = "GEN";
+
private static final String[] GENRES = {
- "",
- TvContract.Programs.Genres.SPORTS,
- TvContract.Programs.Genres.NEWS,
- TvContract.Programs.Genres.SHOPPING,
- TvContract.Programs.Genres.DRAMA,
- TvContract.Programs.Genres.ENTERTAINMENT};
+ "",
+ TvContract.Programs.Genres.SPORTS,
+ TvContract.Programs.Genres.NEWS,
+ TvContract.Programs.Genres.SHOPPING,
+ TvContract.Programs.Genres.DRAMA,
+ TvContract.Programs.Genres.ENTERTAINMENT
+ };
public final String title;
public final String episode;
@@ -118,9 +121,17 @@ public final class ProgramInfo {
return builder.build();
}
- public ProgramInfo(String title, String episode, int seasonNumber, int episodeNumber,
- String posterArtUri, String description, long durationMs,
- TvContentRating[] contentRatings, String genre, String resourceUri) {
+ public ProgramInfo(
+ String title,
+ String episode,
+ int seasonNumber,
+ int episodeNumber,
+ String posterArtUri,
+ String description,
+ long durationMs,
+ TvContentRating[] contentRatings,
+ String genre,
+ String resourceUri) {
this.title = title;
this.episode = episode;
this.seasonNumber = seasonNumber;
@@ -141,8 +152,9 @@ public final class ProgramInfo {
}
/**
- * Get index of the program whose start time equals or less than {@code timeMs} and
- * end time more than {@code timeMs}.
+ * Get index of the program whose start time equals or less than {@code timeMs} and end time
+ * more than {@code timeMs}.
+ *
* @param timeMs target time in millis to find a program.
* @param channelId used to add complexity to the index between two consequence channels.
*/
@@ -151,8 +163,8 @@ public final class ProgramInfo {
return Math.max((int) (timeMs / durationMs), 0);
}
long startTimeMs = channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))];
- int index = (int) ((timeMs - startTimeMs) / DURATIONS_SUM_MS) * DURATIONS_MS.length;
- startTimeMs += (index / DURATIONS_MS.length) * DURATIONS_SUM_MS;
+ int index = (int) ((timeMs - startTimeMs) / durationsSumMs) * DURATIONS_MS.length;
+ startTimeMs += (index / DURATIONS_MS.length) * durationsSumMs;
while (startTimeMs + DURATIONS_MS[index % DURATIONS_MS.length] < timeMs) {
startTimeMs += DURATIONS_MS[index % DURATIONS_MS.length];
index++;
@@ -162,14 +174,16 @@ public final class ProgramInfo {
/**
* Returns the start time for the program with the position.
+ *
* @param index index returned by {@link #getIndex}
*/
public long getStartTimeMs(int index, long channelId) {
if (durationMs != GEN_DURATION) {
return index * durationMs;
}
- long startTimeMs = channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))]
- + (index / DURATIONS_MS.length) * DURATIONS_SUM_MS;
+ long startTimeMs =
+ channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))]
+ + (index / DURATIONS_MS.length) * durationsSumMs;
for (int i = 0; i < index % DURATIONS_MS.length; i++) {
startTimeMs += DURATIONS_MS[i];
}
@@ -177,9 +191,9 @@ public final class ProgramInfo {
}
/**
- * Return complete {@link ProgramInfo} with the generated value.
- * See: {@link #GEN_TITLE}, {@link #GEN_EPISODE}, {@link #GEN_POSTER}, {@link #GEN_DURATION},
- * {@link #GEN_GENRE}.
+ * Return complete {@link ProgramInfo} with the generated value. See: {@link #GEN_TITLE}, {@link
+ * #GEN_EPISODE}, {@link #GEN_POSTER}, {@link #GEN_DURATION}, {@link #GEN_GENRE}.
+ *
* @param index index returned by {@link #getIndex}
*/
public ProgramInfo build(Context context, int index) {
@@ -196,8 +210,8 @@ public final class ProgramInfo {
episode != null ? (index % SEASON_MAX + 1) : seasonNumber,
episode != null ? (index % EPISODE_MAX + 1) : episodeNumber,
GEN_POSTER.equals(posterArtUri)
- ? Utils.getUriStringForResource(context,
- POSTER_ARTS_RES[index % POSTER_ARTS_RES.length])
+ ? Utils.getUriStringForResource(
+ context, POSTER_ARTS_RES[index % POSTER_ARTS_RES.length])
: posterArtUri,
description,
durationMs == GEN_DURATION ? DURATIONS_MS[index % DURATIONS_MS.length] : durationMs,
@@ -208,9 +222,13 @@ public final class ProgramInfo {
@Override
public String toString() {
- return "ProgramInfo{title=" + title
- + ", episode=" + episode
- + ", durationMs=" + durationMs + "}";
+ return "ProgramInfo{title="
+ + title
+ + ", episode="
+ + episode
+ + ", durationMs="
+ + durationMs
+ + "}";
}
@Override
@@ -222,16 +240,16 @@ public final class ProgramInfo {
return false;
}
ProgramInfo that = (ProgramInfo) o;
- return Objects.equals(seasonNumber, that.seasonNumber) &&
- Objects.equals(episodeNumber, that.episodeNumber) &&
- Objects.equals(durationMs, that.durationMs) &&
- Objects.equals(title, that.title) &&
- Objects.equals(episode, that.episode) &&
- Objects.equals(posterArtUri, that.posterArtUri) &&
- Objects.equals(description, that.description) &&
- Objects.equals(genre, that.genre) &&
- Objects.equals(contentRatings, that.contentRatings) &&
- Objects.equals(resourceUri, that.resourceUri);
+ return Objects.equals(seasonNumber, that.seasonNumber)
+ && Objects.equals(episodeNumber, that.episodeNumber)
+ && Objects.equals(durationMs, that.durationMs)
+ && Objects.equals(title, that.title)
+ && Objects.equals(episode, that.episode)
+ && Objects.equals(posterArtUri, that.posterArtUri)
+ && Objects.equals(description, that.description)
+ && Objects.equals(genre, that.genre)
+ && Arrays.equals(contentRatings, that.contentRatings)
+ && Objects.equals(resourceUri, that.resourceUri);
}
@Override
@@ -302,8 +320,17 @@ public final class ProgramInfo {
}
public ProgramInfo build() {
- return new ProgramInfo(mTitle, mEpisode, mSeasonNumber, mEpisodeNumber, mPosterArtUri,
- mDescription, mDurationMs, mContentRatings, mGenre, mResourceUri);
+ return new ProgramInfo(
+ mTitle,
+ mEpisode,
+ mSeasonNumber,
+ mEpisodeNumber,
+ mPosterArtUri,
+ mDescription,
+ mDurationMs,
+ mContentRatings,
+ mGenre,
+ mResourceUri);
}
}
}
diff --git a/tests/common/src/com/android/tv/testing/ProgramUtils.java b/tests/common/src/com/android/tv/testing/data/ProgramUtils.java
index 08c6a033..21647719 100644
--- a/tests/common/src/com/android/tv/testing/ProgramUtils.java
+++ b/tests/common/src/com/android/tv/testing/data/ProgramUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.testing;
+package com.android.tv.testing.data;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -25,36 +25,57 @@ import android.media.tv.TvContract;
import android.media.tv.TvContract.Programs;
import android.net.Uri;
import android.util.Log;
-
import com.android.tv.common.TvContentRatingCache;
-
+import com.android.tv.common.util.Clock;
import java.util.ArrayList;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
-public class ProgramUtils {
+/** Static utilities for using Programs in tests */
+public final class ProgramUtils {
private static final String TAG = "ProgramUtils";
private static final boolean DEBUG = false;
- // Populate program data for a week.
- private static final long PROGRAM_INSERT_DURATION_MS = TimeUnit.DAYS.toMillis(7);
+ /** Populate program data for a week */
+ public static final long PROGRAM_INSERT_DURATION_MS = TimeUnit.DAYS.toMillis(7);
+
private static final int MAX_DB_INSERT_COUNT_AT_ONCE = 500;
/**
- * Populate programs by repeating given program information.
- * This method will populate programs without any gap nor overlapping
- * starting from the current time.
+ * Populate programs by repeating given program information. This method will populate programs
+ * without any gap nor overlapping starting from the current time.
*/
- public static void populatePrograms(Context context, Uri channelUri, ProgramInfo program) {
+ public static void populatePrograms(
+ Context context, Uri channelUri, ProgramInfo program, Clock clock) {
+ populatePrograms(context, channelUri, program, clock, PROGRAM_INSERT_DURATION_MS);
+ }
+
+ public static void populatePrograms(
+ Context context,
+ Uri channelUri,
+ ProgramInfo program,
+ Clock clock,
+ long programInsertDurationMs) {
+ long currentTimeMs = clock.currentTimeMillis();
+ long targetEndTimeMs = currentTimeMs + programInsertDurationMs;
+ populatePrograms(context, channelUri, program, currentTimeMs, targetEndTimeMs);
+ }
+
+ public static void populatePrograms(
+ Context context,
+ Uri channelUri,
+ ProgramInfo program,
+ long currentTimeMs,
+ long targetEndTimeMs) {
ContentValues values = new ContentValues();
long channelId = ContentUris.parseId(channelUri);
values.put(Programs.COLUMN_CHANNEL_ID, channelId);
values.put(Programs.COLUMN_SHORT_DESCRIPTION, program.description);
- values.put(Programs.COLUMN_CONTENT_RATING,
+ values.put(
+ Programs.COLUMN_CONTENT_RATING,
TvContentRatingCache.contentRatingsToString(program.contentRatings));
- long currentTimeMs = System.currentTimeMillis();
- long targetEndTimeMs = currentTimeMs + PROGRAM_INSERT_DURATION_MS;
long timeMs = getLastProgramEndTimeMs(context, channelUri, currentTimeMs, targetEndTimeMs);
if (timeMs <= 0) {
timeMs = currentTimeMs;
@@ -81,11 +102,12 @@ public class ProgramUtils {
list.add(new ContentValues(values));
timeMs += programAt.durationMs;
- if (list.size() >= MAX_DB_INSERT_COUNT_AT_ONCE
- || timeMs >= targetEndTimeMs) {
+ if (list.size() >= MAX_DB_INSERT_COUNT_AT_ONCE || timeMs >= targetEndTimeMs) {
try {
- context.getContentResolver().bulkInsert(Programs.CONTENT_URI,
- list.toArray(new ContentValues[list.size()]));
+ context.getContentResolver()
+ .bulkInsert(
+ Programs.CONTENT_URI,
+ list.toArray(new ContentValues[list.size()]));
} catch (SQLiteException e) {
Log.e(TAG, "Can't insert EPG.", e);
return;
@@ -110,4 +132,16 @@ public class ProgramUtils {
}
private ProgramUtils() {}
+
+ public static void updateProgramForAllChannelsOf(
+ Context context, String inputId, Clock clock, long durationMs) {
+ // Reload channels so we have the ids.
+ Map<Long, ChannelInfo> channelIdToInfoMap =
+ ChannelUtils.queryChannelInfoMapForTvInput(context, inputId);
+ for (Long channelId : channelIdToInfoMap.keySet()) {
+ ProgramInfo programInfo = ProgramInfo.create();
+ populatePrograms(
+ context, TvContract.buildChannelUri(channelId), programInfo, clock, durationMs);
+ }
+ }
}
diff --git a/tests/common/src/com/android/tv/testing/dvr/DvrDataManagerInMemoryImpl.java b/tests/common/src/com/android/tv/testing/dvr/DvrDataManagerInMemoryImpl.java
new file mode 100644
index 00000000..b8a055c7
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/dvr/DvrDataManagerInMemoryImpl.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2015 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.testing.dvr;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Range;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.util.Clock;
+import com.android.tv.dvr.BaseDvrDataManager;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
+import com.android.tv.dvr.data.SeriesRecording;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+/** A DVR Data manager that stores values in memory suitable for testing. */
+public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager {
+ private static final String TAG = "DvrDataManagerInMemory";
+ private final AtomicLong mNextId = new AtomicLong(1);
+ private final Map<Long, ScheduledRecording> mScheduledRecordings = new HashMap<>();
+ private final Map<Long, RecordedProgram> mRecordedPrograms = new HashMap<>();
+ private final Map<Long, SeriesRecording> mSeriesRecordings = new HashMap<>();
+
+ public DvrDataManagerInMemoryImpl(Context context, Clock clock) {
+ super(context, clock);
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return true;
+ }
+
+ @Override
+ public boolean isDvrScheduleLoadFinished() {
+ return true;
+ }
+
+ @Override
+ public boolean isRecordedProgramLoadFinished() {
+ return true;
+ }
+
+ private List<ScheduledRecording> getScheduledRecordingsPrograms() {
+ return new ArrayList<>(mScheduledRecordings.values());
+ }
+
+ @Override
+ public List<RecordedProgram> getRecordedPrograms() {
+ return new ArrayList<>(mRecordedPrograms.values());
+ }
+
+ @Override
+ public List<ScheduledRecording> getAllScheduledRecordings() {
+ return new ArrayList<>(mScheduledRecordings.values());
+ }
+
+ @Override
+ public List<SeriesRecording> getSeriesRecordings() {
+ return new ArrayList<>(mSeriesRecordings.values());
+ }
+
+ @Override
+ public List<SeriesRecording> getSeriesRecordings(String inputId) {
+ List<SeriesRecording> result = new ArrayList<>();
+ for (SeriesRecording r : mSeriesRecordings.values()) {
+ if (TextUtils.equals(r.getInputId(), inputId)) {
+ result.add(r);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public long getNextScheduledStartTimeAfter(long startTime) {
+
+ List<ScheduledRecording> temp = getNonStartedScheduledRecordings();
+ Collections.sort(temp, ScheduledRecording.START_TIME_COMPARATOR);
+ for (ScheduledRecording r : temp) {
+ if (r.getStartTimeMs() > startTime) {
+ return r.getStartTimeMs();
+ }
+ }
+ return DvrDataManager.NEXT_START_TIME_NOT_FOUND;
+ }
+
+ @Override
+ public List<ScheduledRecording> getScheduledRecordings(
+ Range<Long> period, @RecordingState int state) {
+ List<ScheduledRecording> temp = getScheduledRecordingsPrograms();
+ List<ScheduledRecording> result = new ArrayList<>();
+ for (ScheduledRecording r : temp) {
+ if (r.isOverLapping(period) && r.getState() == state) {
+ result.add(r);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public List<ScheduledRecording> getScheduledRecordings(long seriesRecordingId) {
+ List<ScheduledRecording> result = new ArrayList<>();
+ for (ScheduledRecording r : mScheduledRecordings.values()) {
+ if (r.getSeriesRecordingId() == seriesRecordingId) {
+ result.add(r);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public List<ScheduledRecording> getScheduledRecordings(String inputId) {
+ List<ScheduledRecording> result = new ArrayList<>();
+ for (ScheduledRecording r : mScheduledRecordings.values()) {
+ if (TextUtils.equals(r.getInputId(), inputId)) {
+ result.add(r);
+ }
+ }
+ return result;
+ }
+
+ /** Add a new scheduled recording. */
+ @Override
+ public void addScheduledRecording(ScheduledRecording... scheduledRecordings) {
+ addScheduledRecording(false, scheduledRecordings);
+ }
+
+ public void addScheduledRecording(boolean keepIds, ScheduledRecording... scheduledRecordings) {
+ for (ScheduledRecording r : scheduledRecordings) {
+ addScheduledRecordingInternal(r, keepIds);
+ }
+ }
+
+ public void addRecordedProgram(RecordedProgram recordedProgram) {
+ addRecordedProgramInternal(recordedProgram, false);
+ }
+
+ public void updateRecordedProgram(RecordedProgram r) {
+ long id = r.getId();
+ if (mRecordedPrograms.containsKey(id)) {
+ mRecordedPrograms.put(id, r);
+ notifyRecordedProgramsChanged(r);
+ } else {
+ throw new IllegalArgumentException("Recording not found:" + r);
+ }
+ }
+
+ public void removeRecordedProgram(RecordedProgram scheduledRecording) {
+ mRecordedPrograms.remove(scheduledRecording.getId());
+ notifyRecordedProgramsRemoved(scheduledRecording);
+ }
+
+ public ScheduledRecording addScheduledRecordingInternal(ScheduledRecording scheduledRecording) {
+ return addScheduledRecordingInternal(scheduledRecording, false);
+ }
+
+ public ScheduledRecording addScheduledRecordingInternal(
+ ScheduledRecording scheduledRecording, boolean keepId) {
+ if (!keepId) {
+ SoftPreconditions.checkState(
+ scheduledRecording.getId() == ScheduledRecording.ID_NOT_SET,
+ TAG,
+ "expected id of "
+ + ScheduledRecording.ID_NOT_SET
+ + " but was "
+ + scheduledRecording);
+ scheduledRecording =
+ ScheduledRecording.buildFrom(scheduledRecording)
+ .setId(mNextId.incrementAndGet())
+ .build();
+ }
+ mScheduledRecordings.put(scheduledRecording.getId(), scheduledRecording);
+ notifyScheduledRecordingAdded(scheduledRecording);
+ return scheduledRecording;
+ }
+
+ public RecordedProgram addRecordedProgramInternal(
+ RecordedProgram recordedProgram, boolean keepId) {
+ if (!keepId) {
+ SoftPreconditions.checkState(
+ recordedProgram.getId() == RecordedProgram.ID_NOT_SET,
+ TAG,
+ "expected id of " + RecordedProgram.ID_NOT_SET + " but was " + recordedProgram);
+ recordedProgram =
+ RecordedProgram
+ .buildFrom(recordedProgram)
+ .setId(mNextId.incrementAndGet())
+ .build();
+ }
+ mRecordedPrograms.put(recordedProgram.getId(), recordedProgram);
+ notifyRecordedProgramsAdded(recordedProgram);
+ return recordedProgram;
+ }
+
+ @Override
+ public void addSeriesRecording(SeriesRecording... seriesRecordings) {
+ for (SeriesRecording r : seriesRecordings) {
+ mSeriesRecordings.put(r.getId(), r);
+ }
+ notifySeriesRecordingAdded(seriesRecordings);
+ }
+
+ @Override
+ public void removeScheduledRecording(ScheduledRecording... scheduledRecordings) {
+ for (ScheduledRecording r : scheduledRecordings) {
+ mScheduledRecordings.remove(r.getId());
+ }
+ notifyScheduledRecordingRemoved(scheduledRecordings);
+ }
+
+ @Override
+ public void removeScheduledRecording(boolean forceRemove, ScheduledRecording... schedule) {
+ removeScheduledRecording(schedule);
+ }
+
+ @Override
+ public void removeSeriesRecording(SeriesRecording... seriesRecordings) {
+ for (SeriesRecording r : seriesRecordings) {
+ mSeriesRecordings.remove(r.getId());
+ }
+ notifySeriesRecordingRemoved(seriesRecordings);
+ }
+
+ @Override
+ public void updateScheduledRecording(ScheduledRecording... scheduledRecordings) {
+ for (ScheduledRecording r : scheduledRecordings) {
+ long id = r.getId();
+ if (mScheduledRecordings.containsKey(id)) {
+ mScheduledRecordings.put(id, r);
+ } else {
+ Log.d(TAG, "Recording not found:" + r);
+ }
+ }
+ notifyScheduledRecordingStatusChanged(scheduledRecordings);
+ }
+
+ @Override
+ public void updateSeriesRecording(SeriesRecording... seriesRecordings) {
+ for (SeriesRecording r : seriesRecordings) {
+ long id = r.getId();
+ if (mSeriesRecordings.containsKey(id)) {
+ mSeriesRecordings.put(id, r);
+ } else {
+ throw new IllegalArgumentException("Recording not found:" + r);
+ }
+ }
+ notifySeriesRecordingChanged(seriesRecordings);
+ }
+
+ @Nullable
+ @Override
+ public ScheduledRecording getScheduledRecording(long id) {
+ return mScheduledRecordings.get(id);
+ }
+
+ @Nullable
+ @Override
+ public ScheduledRecording getScheduledRecordingForProgramId(long programId) {
+ for (ScheduledRecording r : mScheduledRecordings.values()) {
+ if (r.getProgramId() == programId) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public SeriesRecording getSeriesRecording(long seriesRecordingId) {
+ return mSeriesRecordings.get(seriesRecordingId);
+ }
+
+ @Nullable
+ @Override
+ public SeriesRecording getSeriesRecording(String seriesId) {
+ for (SeriesRecording r : mSeriesRecordings.values()) {
+ if (r.getSeriesId().equals(seriesId)) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public RecordedProgram getRecordedProgram(long recordingId) {
+ return mRecordedPrograms.get(recordingId);
+ }
+
+ @Override
+ @NonNull
+ protected List<ScheduledRecording> getRecordingsWithState(int... states) {
+ ArrayList<ScheduledRecording> result = new ArrayList<>();
+ for (ScheduledRecording r : mScheduledRecordings.values()) {
+ for (int state : states) {
+ if (r.getState() == state) {
+ result.add(r);
+ break;
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java b/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java
index a9bfa97a..72bac8fc 100644
--- a/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java
+++ b/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java
@@ -17,40 +17,37 @@
package com.android.tv.testing.dvr;
import com.android.tv.dvr.data.ScheduledRecording;
-
import junit.framework.Assert;
-/**
- * Static utils for using {@link ScheduledRecording} in tests.
- */
+/** Static utils for using {@link ScheduledRecording} in tests. */
public final class RecordingTestUtils {
private static final String INPUT_ID = "input_id";
private static final int CHANNEL_ID = 273;
- public static ScheduledRecording createTestRecordingWithIdAndPeriod(long id, String inputId,
- long channelId, long startTime, long endTime) {
+ public static ScheduledRecording createTestRecordingWithIdAndPeriod(
+ long id, String inputId, long channelId, long startTime, long endTime) {
return ScheduledRecording.builder(inputId, channelId, startTime, endTime)
.setId(id)
.setChannelId(channelId)
.build();
}
- public static ScheduledRecording createTestRecordingWithPeriod(String inputId,
- long channelId, long startTime, long endTime) {
- return createTestRecordingWithIdAndPeriod(ScheduledRecording.ID_NOT_SET, inputId, channelId,
- startTime, endTime);
+ public static ScheduledRecording createTestRecordingWithPeriod(
+ String inputId, long channelId, long startTime, long endTime) {
+ return createTestRecordingWithIdAndPeriod(
+ ScheduledRecording.ID_NOT_SET, inputId, channelId, startTime, endTime);
}
- public static ScheduledRecording createTestRecordingWithPriorityAndPeriod(long channelId,
- long priority, long startTime, long endTime) {
+ public static ScheduledRecording createTestRecordingWithPriorityAndPeriod(
+ long channelId, long priority, long startTime, long endTime) {
return ScheduledRecording.builder(INPUT_ID, CHANNEL_ID, startTime, endTime)
.setChannelId(channelId)
.setPriority(priority)
.build();
}
- public static ScheduledRecording createTestRecordingWithIdAndPriorityAndPeriod(long id,
- long channelId, long priority, long startTime, long endTime) {
+ public static ScheduledRecording createTestRecordingWithIdAndPriorityAndPeriod(
+ long id, long channelId, long priority, long startTime, long endTime) {
return ScheduledRecording.builder(INPUT_ID, CHANNEL_ID, startTime, endTime)
.setId(id)
.setChannelId(channelId)
@@ -58,11 +55,12 @@ public final class RecordingTestUtils {
.build();
}
- public static ScheduledRecording normalizePriority(ScheduledRecording orig){
+ public static ScheduledRecording normalizePriority(ScheduledRecording orig) {
return ScheduledRecording.buildFrom(orig).setPriority(orig.getId()).build();
}
- public static void assertRecordingEquals(ScheduledRecording expected, ScheduledRecording actual) {
+ public static void assertRecordingEquals(
+ ScheduledRecording expected, ScheduledRecording actual) {
Assert.assertEquals("id", expected.getId(), actual.getId());
Assert.assertEquals("channel", expected.getChannelId(), actual.getChannelId());
Assert.assertEquals("programId", expected.getProgramId(), actual.getProgramId());
@@ -70,9 +68,11 @@ public final class RecordingTestUtils {
Assert.assertEquals("start time", expected.getStartTimeMs(), actual.getStartTimeMs());
Assert.assertEquals("end time", expected.getEndTimeMs(), actual.getEndTimeMs());
Assert.assertEquals("state", expected.getState(), actual.getState());
- Assert.assertEquals("parent series recording", expected.getSeriesRecordingId(),
+ Assert.assertEquals(
+ "parent series recording",
+ expected.getSeriesRecordingId(),
actual.getSeriesRecordingId());
}
- private RecordingTestUtils() { }
+ private RecordingTestUtils() {}
}
diff --git a/tests/common/src/com/android/tv/testing/robo/ContentProviders.java b/tests/common/src/com/android/tv/testing/robo/ContentProviders.java
new file mode 100644
index 00000000..aaaa11df
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/robo/ContentProviders.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.testing.robo;
+
+import android.content.ContentProvider;
+import android.content.pm.ProviderInfo;
+import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ContentProviderController;
+import org.robolectric.shadows.ShadowContentResolver;
+
+/** Static utilities for using content providers in tests. */
+public final class ContentProviders {
+
+ /** Builds creates and register a ContentProvider with the given authority. */
+ public static <T extends ContentProvider> T register(Class<T> providerClass, String authority) {
+ ProviderInfo info = new ProviderInfo();
+ info.authority = authority;
+ ContentProviderController<T> contentProviderController =
+ Robolectric.buildContentProvider(providerClass);
+ T provider = contentProviderController.create(info).get();
+ provider.onCreate();
+ ShadowContentResolver.registerProviderInternal(authority, provider);
+ return provider;
+ }
+
+ private ContentProviders() {}
+}
diff --git a/tests/common/src/com/android/tv/testing/robo/RobotTestAppHelper.java b/tests/common/src/com/android/tv/testing/robo/RobotTestAppHelper.java
new file mode 100644
index 00000000..9eb79298
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/robo/RobotTestAppHelper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 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.testing.robo;
+
+import android.media.tv.TvContract;
+import com.android.tv.testing.FakeTvProvider;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.testdata.TestData;
+import java.util.concurrent.TimeUnit;
+import org.robolectric.Robolectric;
+
+/** Static utilities for using {@link TestSingletonApp} in roboletric tests. */
+public final class RobotTestAppHelper {
+
+ public static void loadTestData(TestSingletonApp app, TestData testData) {
+ ContentProviders.register(FakeTvProvider.class, TvContract.AUTHORITY);
+ app.loadTestData(testData, TimeUnit.DAYS.toMillis(1));
+ Robolectric.flushBackgroundThreadScheduler();
+ Robolectric.flushForegroundThreadScheduler();
+ }
+
+ private RobotTestAppHelper() {}
+}
diff --git a/tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java b/tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java
new file mode 100644
index 00000000..5a2c41e6
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 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.testing.shadows;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** Shadow {@link MediaSession}. */
+@Implements(MediaSession.class)
+public class ShadowMediaSession {
+
+ public MediaSession.Callback mCallback;
+ public PendingIntent mMediaButtonReceiver;
+ public PendingIntent mSessionActivity;
+ public PlaybackState mPlaybackState;
+ public MediaMetadata mMediaMetadata;
+ public int mFlags;
+ public boolean mActive;
+ public boolean mReleased;
+
+ /** Stand-in for the MediaSession constructor with the same parameters. */
+ public void __constructor__(Context context, String tag, int userID) {
+ // This empty method prevents the real MediaSession constructor from being called.
+ }
+
+ @Implementation
+ public void setCallback(MediaSession.Callback callback) {
+ mCallback = callback;
+ }
+
+ @Implementation
+ public void setMediaButtonReceiver(PendingIntent mbr) {
+ mMediaButtonReceiver = mbr;
+ }
+
+ @Implementation
+ public void setSessionActivity(PendingIntent activity) {
+ mSessionActivity = activity;
+ }
+
+ @Implementation
+ public void setPlaybackState(PlaybackState state) {
+ mPlaybackState = state;
+ }
+
+ @Implementation
+ public void setMetadata(MediaMetadata metadata) {
+ mMediaMetadata = metadata;
+ }
+
+ @Implementation
+ public void setFlags(int flags) {
+ mFlags = flags;
+ }
+
+ @Implementation
+ public boolean isActive() {
+ return mActive;
+ }
+
+ @Implementation
+ public void setActive(boolean active) {
+ mActive = active;
+ }
+
+ @Implementation
+ public void release() {
+ mReleased = true;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/testdata/TestData.java b/tests/common/src/com/android/tv/testing/testdata/TestData.java
new file mode 100644
index 00000000..e7e52348
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/testdata/TestData.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2018 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.testing.testdata;
+
+import android.content.Context;
+import android.media.tv.TvInputInfo;
+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.ProgramUtils;
+import com.android.tv.testing.utils.TestUtils;
+import java.util.List;
+
+/**
+ * A set of test data.
+ *
+ * <p>contains:
+ *
+ * <ul>
+ * <li>InputID
+ * <li>Channel List
+ * </ul>
+ *
+ * Call {@link #init(Context)}, to update the TvProvider data base with the given values.
+ */
+public abstract class TestData {
+ private List<ChannelInfo> channelList;
+
+ protected abstract List<ChannelInfo> createChannels(Context context);
+
+ public void init(Context context, Clock clock, long durationMs) {
+ channelList = createChannels(context);
+ ChannelUtils.updateChannels(context, getInputId(), channelList);
+ ProgramUtils.updateProgramForAllChannelsOf(context, getInputId(), clock, durationMs);
+ }
+
+ public abstract TvInputInfo getTvInputInfo();
+
+ public final String getInputId() {
+ return getTvInputInfo().getId();
+ }
+
+ public static final TestData DEFAULT_10_CHANNELS =
+ new TestData() {
+ private TvInputInfo mTvInputInfo = createTvInputInfo();
+
+ private TvInputInfo createTvInputInfo() {
+ try {
+ return TestUtils.createTvInputInfo(
+ TestUtils.createResolveInfo(
+ "com.android.tv.testing.testdata",
+ "com.android.tv.testing.testdata.Default10Channels"),
+ "com.android.tv.testing.testdata/.Default10Channels",
+ null,
+ TvInputInfo.TYPE_TUNER,
+ true);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected List<ChannelInfo> createChannels(Context context) {
+ return ChannelUtils.createChannelInfos(context, 10);
+ }
+
+ @Override
+ public TvInputInfo getTvInputInfo() {
+ return mTvInputInfo;
+ }
+ };
+}
diff --git a/tests/common/src/com/android/tv/testing/testinput/ChannelState.java b/tests/common/src/com/android/tv/testing/testinput/ChannelState.java
index 1b8f63cd..3e8bab33 100644
--- a/tests/common/src/com/android/tv/testing/testinput/ChannelState.java
+++ b/tests/common/src/com/android/tv/testing/testinput/ChannelState.java
@@ -16,24 +16,16 @@
package com.android.tv.testing.testinput;
import android.media.tv.TvTrackInfo;
-
-import com.android.tv.testing.Constants;
-
+import com.android.tv.testing.constants.Constants;
import java.util.Collections;
import java.util.List;
-/**
- * Versioned state information for a channel.
- */
+/** Versioned state information for a channel. */
public class ChannelState {
- /**
- * The video track a channel has by default.
- */
+ /** The video track a channel has by default. */
public static final TvTrackInfo DEFAULT_VIDEO_TRACK = Constants.FHD1080P50_VIDEO_TRACK;
- /**
- * The video track a channel has by default.
- */
+ /** The video track a channel has by default. */
public static final TvTrackInfo DEFAULT_AUDIO_TRACK = Constants.EN_STEREO_AUDIO_TRACK;
/**
* The channel is "tuned" and video available.
@@ -47,27 +39,23 @@ public class ChannelState {
* Default ChannelState with version @{value #CHANNEL_VERSION_DEFAULT} and default {@link
* ChannelStateData}.
*/
- public static final ChannelState DEFAULT = new ChannelState(CHANNEL_VERSION_DEFAULT,
- new ChannelStateData());
+ public static final ChannelState DEFAULT =
+ new ChannelState(CHANNEL_VERSION_DEFAULT, new ChannelStateData());
+
private final int mVersion;
private final ChannelStateData mData;
-
private ChannelState(int version, ChannelStateData channelStateData) {
mVersion = version;
mData = channelStateData;
}
- /**
- * Returns the id of the selected audio track, or null if none is selected.
- */
+ /** Returns the id of the selected audio track, or null if none is selected. */
public String getSelectedAudioTrackId() {
return mData.mSelectedAudioTrackId;
}
- /**
- * Returns the id of the selected audio track, or null if none is selected.
- */
+ /** Returns the id of the selected audio track, or null if none is selected. */
public String getSelectedVideoTrackId() {
return mData.mSelectedVideoTrackId;
}
@@ -82,9 +70,8 @@ public class ChannelState {
}
/**
- * Tune status is either {@link #TUNE_STATUS_VIDEO_AVAILABLE} or a {@link
- * android.media.tv.TvInputService.Session#notifyVideoUnavailable(int) video unavailable
- * reason}
+ * Tune status is either {@link #TUNE_STATUS_VIDEO_AVAILABLE} or a {@link
+ * android.media.tv.TvInputService.Session#notifyVideoUnavailable(int) video unavailable reason}
*/
public int getTuneStatus() {
return mData.mTuneStatus;
diff --git a/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java b/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java
index 9bac9d12..cdeb1f5c 100644
--- a/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java
+++ b/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java
@@ -19,25 +19,23 @@ package com.android.tv.testing.testinput;
import android.media.tv.TvTrackInfo;
import android.os.Parcel;
import android.os.Parcelable;
-
import java.util.ArrayList;
import java.util.List;
-/**
- * Mutable unversioned channel state.
- */
+/** Mutable unversioned channel state. */
public final class ChannelStateData implements Parcelable {
- public static final Creator<ChannelStateData> CREATOR = new Creator<ChannelStateData>() {
- @Override
- public ChannelStateData createFromParcel(Parcel in) {
- return new ChannelStateData(in);
- }
+ public static final Creator<ChannelStateData> CREATOR =
+ new Creator<ChannelStateData>() {
+ @Override
+ public ChannelStateData createFromParcel(Parcel in) {
+ return new ChannelStateData(in);
+ }
- @Override
- public ChannelStateData[] newArray(int size) {
- return new ChannelStateData[size];
- }
- };
+ @Override
+ public ChannelStateData[] newArray(int size) {
+ return new ChannelStateData[size];
+ }
+ };
public final List<TvTrackInfo> mTvTrackInfos = new ArrayList<>();
public int mTuneStatus = ChannelState.TUNE_STATUS_VIDEO_AVAILABLE;
@@ -71,9 +69,6 @@ public final class ChannelStateData implements Parcelable {
@Override
public String toString() {
- return "{"
- + "tune=" + mTuneStatus
- + ", tracks=" + mTvTrackInfos
- + "}";
+ return "{" + "tune=" + mTuneStatus + ", tracks=" + mTvTrackInfos + "}";
}
}
diff --git a/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java b/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java
index 9b3f8835..071b1d3c 100644
--- a/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java
+++ b/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java
@@ -21,8 +21,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
-
-import com.android.tv.testing.ChannelInfo;
+import com.android.tv.testing.data.ChannelInfo;
/**
* Connection for controlling the Test TV Input Service.
@@ -47,9 +46,7 @@ public class TestInputControlConnection implements ServiceConnection {
mControl = null;
}
- /**
- * Is the service currently connected.
- */
+ /** Is the service currently connected. */
public boolean isBound() {
return mControl != null;
}
@@ -58,7 +55,7 @@ public class TestInputControlConnection implements ServiceConnection {
* Update the state of the channel.
*
* @param channel the channel to update.
- * @param data the new state for the channel.
+ * @param data the new state for the channel.
*/
public void updateChannelState(ChannelInfo channel, ChannelStateData data) {
waitUntilBound();
@@ -69,9 +66,7 @@ public class TestInputControlConnection implements ServiceConnection {
}
}
- /**
- * Sleep until {@link #isBound()} is true;
- */
+ /** Sleep until {@link #isBound()} is true; */
public void waitUntilBound() {
while (!isBound()) {
SystemClock.sleep(BOUND_CHECK_INTERVAL_MS);
diff --git a/tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java b/tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java
index 54aacf20..330afa9b 100644
--- a/tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java
+++ b/tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java
@@ -18,16 +18,16 @@ package com.android.tv.testing.testinput;
import android.content.ComponentName;
import android.content.Intent;
-/**
- * Static utils for {@link ITestInputControl}.
- */
+/** Static utils for {@link ITestInputControl}. */
public final class TestInputControlUtils {
public static Intent createIntent() {
- return new Intent().setComponent(new ComponentName("com.android.tv.testinput",
- "com.android.tv.testinput.TestInputControlService"));
+ return new Intent()
+ .setComponent(
+ new ComponentName(
+ "com.android.tv.testinput",
+ "com.android.tv.testinput.TestInputControlService"));
}
- private TestInputControlUtils() {
- }
+ private TestInputControlUtils() {}
}
diff --git a/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java b/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java
index 498addfd..27d3036c 100644
--- a/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java
+++ b/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java
@@ -15,25 +15,22 @@
*/
package com.android.tv.testing.testinput;
-import com.android.tv.testing.ChannelInfo;
+import com.android.tv.testing.data.ChannelInfo;
-/**
- * Constants for interacting with TvTestInput.
- */
+/** Constants for interacting with TvTestInput. */
public final class TvTestInputConstants {
/**
* Channel 1.
*
- * <p> By convention Channel 1 should not be changed. Test often start by tuning to this
- * channel.
+ * <p>By convention Channel 1 should not be changed. Test often start by tuning to this channel.
*/
public static final ChannelInfo CH_1_DEFAULT_DONT_MODIFY = ChannelInfo.create(null, 1);
/**
* Channel 2.
*
- * <p> By convention the state of Channel 2 is changed by tests. Testcases should explicitly
- * set the state of this channel before using it in tests.
+ * <p>By convention the state of Channel 2 is changed by tests. Testcases should explicitly set
+ * the state of this channel before using it in tests.
*/
public static final ChannelInfo CH_2 = ChannelInfo.create(null, 2);
}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java b/tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java
index 3a2f5509..21b05d67 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java
@@ -18,9 +18,7 @@ package com.android.tv.testing.uihelper;
import android.content.res.Resources;
import android.support.test.uiautomator.UiDevice;
-/**
- * Base class for building UiAutomator Helper classes.
- */
+/** Base class for building UiAutomator Helper classes. */
public abstract class BaseUiDeviceHelper {
protected final UiDevice mUiDevice;
protected final Resources mTargetResources;
diff --git a/tests/common/src/com/android/tv/testing/uihelper/ByResource.java b/tests/common/src/com/android/tv/testing/uihelper/ByResource.java
index a76ee1d3..47b8d9f9 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/ByResource.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/ByResource.java
@@ -19,9 +19,7 @@ import android.content.res.Resources;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
-/**
- * Convenience methods for creating {@link BySelector}s using resource ids.
- */
+/** Convenience methods for creating {@link BySelector}s using resource ids. */
public final class ByResource {
/**
@@ -44,6 +42,5 @@ public final class ByResource {
return By.text(text);
}
- private ByResource() {
- }
+ private ByResource() {}
}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/Constants.java b/tests/common/src/com/android/tv/testing/uihelper/Constants.java
index 8dd8e14a..4b522914 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/Constants.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/Constants.java
@@ -17,6 +17,7 @@ package com.android.tv.testing.uihelper;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
+import com.android.tv.common.CommonConstants;
public final class Constants {
@@ -24,7 +25,7 @@ public final class Constants {
public static final int MIN_EXTRA_TIMEOUT = 10;
public static final long MAX_SHOW_DELAY_MILLIS = 200;
public static final long MAX_FOCUSED_DELAY_MILLIS = 1000;
- public static final String TV_APP_PACKAGE = "com.android.tv";
+ public static final String TV_APP_PACKAGE = CommonConstants.BASE_PACKAGE;
public static final BySelector TV_VIEW = By.res(TV_APP_PACKAGE, "main_tunable_tv_view");
public static final BySelector CHANNEL_BANNER = By.res(TV_APP_PACKAGE, "channel_banner_view");
public static final BySelector KEYPAD_CHANNEL_SWITCH = By.res(TV_APP_PACKAGE, "channel_number");
@@ -35,6 +36,5 @@ public final class Constants {
public static final BySelector DVR_SCHEDULES = By.res(TV_APP_PACKAGE, "dvr_schedules");
public static final BySelector FOCUSED_VIEW = By.focused(true);
- private Constants() {
- }
+ private Constants() {}
}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java b/tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java
index 9e4040a8..2ac4b648 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java
@@ -24,12 +24,9 @@ import android.content.res.Resources;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
-
import com.android.tv.R;
-/**
- * Helper for testing {@link DialogFragment}s.
- */
+/** Helper for testing {@link DialogFragment}s. */
public class DialogHelper extends BaseUiDeviceHelper {
private final BySelector byPinDialog;
@@ -39,7 +36,9 @@ public class DialogHelper extends BaseUiDeviceHelper {
}
public void assertWaitForPinDialogOpen() {
- assertWaitForCondition(mUiDevice, Until.hasObject(byPinDialog),
+ assertWaitForCondition(
+ mUiDevice,
+ Until.hasObject(byPinDialog),
Constants.MAX_SHOW_DELAY_MILLIS
+ mTargetResources.getInteger(R.integer.pin_dialog_anim_duration));
}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
index 1dc0f020..4b7c1f89 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2017 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.testing.uihelper;
import static com.android.tv.testing.uihelper.UiDeviceAsserts.waitForCondition;
@@ -11,31 +26,28 @@ import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
import android.util.Log;
-
-import com.android.tv.testing.Utils;
-
+import com.android.tv.common.CommonConstants;
+import com.android.tv.testing.utils.Utils;
import junit.framework.Assert;
-/**
- * Helper for testing the Live TV Application.
- */
+/** Helper for testing the Live TV Application. */
public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper {
private static final String TAG = "LiveChannelsUiDevice";
private static final int APPLICATION_START_TIMEOUT_MSEC = 5000;
private final Context mContext;
- public LiveChannelsUiDeviceHelper(UiDevice uiDevice, Resources targetResources,
- Context context) {
+ public LiveChannelsUiDeviceHelper(
+ UiDevice uiDevice, Resources targetResources, Context context) {
super(uiDevice, targetResources);
mContext = context;
}
public void assertAppStarted() {
assertTrue("TvActivity should be enabled.", Utils.isTvActivityEnabled(mContext));
- Intent intent = mContext.getPackageManager()
- .getLaunchIntentForPackage(Constants.TV_APP_PACKAGE);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances
+ Intent intent =
+ mContext.getPackageManager().getLaunchIntentForPackage(Constants.TV_APP_PACKAGE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances
mContext.startActivity(intent);
// Wait for idle state before checking the channel banner because waitForCondition() has
// timeout.
@@ -43,8 +55,10 @@ public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper {
// Make sure that the activity is resumed.
waitForCondition(mUiDevice, Until.hasObject(Constants.TV_VIEW));
- Assert.assertTrue(Constants.TV_APP_PACKAGE + " did not start", mUiDevice
- .wait(Until.hasObject(By.pkg(Constants.TV_APP_PACKAGE).depth(0)),
+ Assert.assertTrue(
+ Constants.TV_APP_PACKAGE + " did not start",
+ mUiDevice.wait(
+ Until.hasObject(By.pkg(Constants.TV_APP_PACKAGE).depth(0)),
APPLICATION_START_TIMEOUT_MSEC));
BySelector welcome = ByResource.id(mTargetResources, com.android.tv.R.id.intro);
if (mUiDevice.hasObject(welcome)) {
@@ -54,9 +68,9 @@ public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper {
}
public void assertAppStopped() {
- while(mUiDevice.hasObject(By.pkg(Constants.TV_APP_PACKAGE).depth(0))) {
+ while (mUiDevice.hasObject(By.pkg(CommonConstants.BASE_PACKAGE).depth(0))) {
mUiDevice.pressBack();
mUiDevice.waitForIdle();
}
}
-} \ No newline at end of file
+}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java b/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java
index 80d53242..c8ea85ac 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java
@@ -25,33 +25,30 @@ import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
-
import com.android.tv.R;
-
import junit.framework.Assert;
-/**
- * Helper for testing {@link com.android.tv.menu.Menu}.
- */
+/** Helper for testing {@link com.android.tv.menu.Menu}. */
public class MenuHelper extends BaseUiDeviceHelper {
private final BySelector byChannels;
public MenuHelper(UiDevice uiDevice, Resources targetResources) {
super(uiDevice, targetResources);
- byChannels = ByResource.id(mTargetResources, R.id.item_list)
- .hasDescendant(ByResource.text(mTargetResources, R.string.menu_title_channels));
+ byChannels =
+ ByResource.id(mTargetResources, R.id.item_list)
+ .hasDescendant(
+ ByResource.text(mTargetResources, R.string.menu_title_channels));
}
public BySelector getByChannels() {
return byChannels;
}
-
/**
- * Navigate to the menu item with the text {@code itemTextResId} in the row with text
- * {@code rowTitleResId}.
- * <p>
- * Fails if the menu item can not be navigated to.
+ * Navigate to the menu item with the text {@code itemTextResId} in the row with text {@code
+ * rowTitleResId}.
+ *
+ * <p>Fails if the menu item can not be navigated to.
*
* @param rowTitleResId the resource id of the string in the desired row title.
* @param itemTextResId the resource id of the string in the desired item.
@@ -62,17 +59,20 @@ public class MenuHelper extends BaseUiDeviceHelper {
BySelector byListView = ByResource.id(mTargetResources, R.id.list_view);
UiObject2 listView = row.findObject(byListView);
Assert.assertNotNull(
- "Menu row '" + mTargetResources.getString(rowTitleResId) + "' does not have a "
- + byListView, listView);
+ "Menu row '"
+ + mTargetResources.getString(rowTitleResId)
+ + "' does not have a "
+ + byListView,
+ listView);
return assertNavigateToRowItem(listView, itemTextResId);
}
/**
* Navigate to the menu row with the text title {@code rowTitleResId}.
- * <p>
- * Fails if the menu row can not be navigated to.
- * We can't navigate to the Play controls row with this method, because the row doesn't have the
- * title when it is selected. Use {@link #assertNavigateToPlayControlsRow} for the row instead.
+ *
+ * <p>Fails if the menu row can not be navigated to. We can't navigate to the Play controls row
+ * with this method, because the row doesn't have the title when it is selected. Use {@link
+ * #assertNavigateToPlayControlsRow} for the row instead.
*
* @param rowTitleResId the resource id of the string in the desired row title.
* @return the row navigated to.
@@ -82,14 +82,17 @@ public class MenuHelper extends BaseUiDeviceHelper {
UiObject2 menu = mUiDevice.findObject(MENU);
// TODO: handle play controls. They have a different dom structure and navigation sometimes
// can get stuck on that row.
- return UiDeviceAsserts.assertNavigateTo(mUiDevice, menu,
- By.hasDescendant(ByResource.text(mTargetResources, rowTitleResId)), Direction.DOWN);
+ return UiDeviceAsserts.assertNavigateTo(
+ mUiDevice,
+ menu,
+ By.hasDescendant(ByResource.text(mTargetResources, rowTitleResId)),
+ Direction.DOWN);
}
/**
* Navigate to the Play controls row.
- * <p>
- * Fails if the row can not be navigated to.
+ *
+ * <p>Fails if the row can not be navigated to.
*
* @see #assertNavigateToRow
*/
@@ -103,27 +106,28 @@ public class MenuHelper extends BaseUiDeviceHelper {
/**
* Navigate to the menu item in the given {@code row} with the text {@code itemTextResId} .
- * <p>
- * Fails if the menu item can not be navigated to.
*
- * @param row the container to look for menu items in.
+ * <p>Fails if the menu item can not be navigated to.
+ *
+ * @param row the container to look for menu items in.
* @param itemTextResId the resource id of the string in the desired item.
* @return the item navigated to.
*/
public UiObject2 assertNavigateToRowItem(UiObject2 row, int itemTextResId) {
- return UiDeviceAsserts.assertNavigateTo(mUiDevice, row,
+ return UiDeviceAsserts.assertNavigateTo(
+ mUiDevice,
+ row,
By.hasDescendant(ByResource.text(mTargetResources, itemTextResId)),
Direction.RIGHT);
}
public UiObject2 assertPressOptionsSettings() {
- return assertPressMenuItem(R.string.menu_title_options,
- R.string.options_item_settings);
+ return assertPressMenuItem(R.string.menu_title_options, R.string.options_item_settings);
}
public UiObject2 assertPressOptionsClosedCaptions() {
- return assertPressMenuItem(R.string.menu_title_options,
- R.string.options_item_closed_caption);
+ return assertPressMenuItem(
+ R.string.menu_title_options, R.string.options_item_closed_caption);
}
public UiObject2 assertPressOptionsDisplayMode() {
@@ -135,20 +139,19 @@ public class MenuHelper extends BaseUiDeviceHelper {
}
public UiObject2 assertPressProgramGuide() {
- return assertPressMenuItem(R.string.menu_title_channels,
- R.string.channels_item_program_guide);
+ return assertPressMenuItem(
+ R.string.menu_title_channels, R.string.channels_item_program_guide);
}
public UiObject2 assertPressDvrLibrary() {
- return assertPressMenuItem(R.string.menu_title_channels,
- R.string.channels_item_dvr);
+ return assertPressMenuItem(R.string.menu_title_channels, R.string.channels_item_dvr);
}
/**
- * Navigate to the menu item with the text {@code itemTextResId} in the row with text
- * {@code rowTitleResId}.
- * <p>
- * Fails if the menu item can not be navigated to.
+ * Navigate to the menu item with the text {@code itemTextResId} in the row with text {@code
+ * rowTitleResId}.
+ *
+ * <p>Fails if the menu item can not be navigated to.
*
* @param rowTitleResId the resource id of the string in the desired row title.
* @param itemTextResId the resource id of the string in the desired item.
@@ -161,17 +164,15 @@ public class MenuHelper extends BaseUiDeviceHelper {
return item;
}
- /**
- * Waits until the menu is visible.
- */
+ /** Waits until the menu is visible. */
public void assertWaitForMenu() {
UiDeviceAsserts.assertWaitForCondition(mUiDevice, Until.hasObject(MENU));
}
/**
- * Show the menu.
- * <p>
- * Fails if the menu does not appear in {@link Constants#MAX_SHOW_DELAY_MILLIS}.
+ * Show the menu.
+ *
+ * <p>Fails if the menu does not appear in {@link Constants#MAX_SHOW_DELAY_MILLIS}.
*/
public void showMenu() {
if (!mUiDevice.hasObject(MENU)) {
diff --git a/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java b/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java
index 98a19a41..ba015260 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java
@@ -22,15 +22,11 @@ import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
-
import com.android.tv.R;
import com.android.tv.ui.sidepanel.SideFragment;
-
import junit.framework.Assert;
-/**
- * Helper for testing {@link SideFragment}s.
- */
+/** Helper for testing {@link SideFragment}s. */
public class SidePanelHelper extends BaseUiDeviceHelper {
public SidePanelHelper(UiDevice uiDevice, Resources targetResources) {
@@ -54,6 +50,7 @@ public class SidePanelHelper extends BaseUiDeviceHelper {
String title = mTargetResources.getString(resId);
return assertNavigateToItem(title, direction);
}
+
public UiObject2 assertNavigateToItem(String title) {
return assertNavigateToItem(title, Direction.DOWN);
}
@@ -63,7 +60,7 @@ public class SidePanelHelper extends BaseUiDeviceHelper {
UiObject2 sidePanelList = mUiDevice.findObject(sidePanelSelector);
Assert.assertNotNull(sidePanelSelector + " not found", sidePanelList);
- return UiDeviceAsserts.assertNavigateTo(mUiDevice, sidePanelList,
- By.hasDescendant(By.text(title)), direction);
+ return UiDeviceAsserts.assertNavigateTo(
+ mUiDevice, sidePanelList, By.hasDescendant(By.text(title)), direction);
}
}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java
index c096d7d2..28ea163e 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java
@@ -27,12 +27,9 @@ import android.support.test.uiautomator.SearchCondition;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
-
import junit.framework.Assert;
-/**
- * Asserts for {@link UiDevice}s.
- */
+/** Asserts for {@link UiDevice}s. */
public final class UiDeviceAsserts {
public static void assertHas(UiDevice uiDevice, BySelector bySelector, boolean expected) {
@@ -46,25 +43,25 @@ public final class UiDeviceAsserts {
}
/**
- * Assert that {@code searchCondition} becomes true within
- * {@value Constants#MAX_SHOW_DELAY_MILLIS} milliseconds.
+ * Assert that {@code searchCondition} becomes true within {@value
+ * Constants#MAX_SHOW_DELAY_MILLIS} milliseconds.
*
- * @param uiDevice the device under test.
+ * @param uiDevice the device under test.
* @param searchCondition the condition to wait for.
*/
- public static void assertWaitForCondition(UiDevice uiDevice,
- SearchCondition<Boolean> searchCondition) {
+ public static void assertWaitForCondition(
+ UiDevice uiDevice, SearchCondition<Boolean> searchCondition) {
assertWaitForCondition(uiDevice, searchCondition, Constants.MAX_SHOW_DELAY_MILLIS);
}
/**
* Assert that {@code searchCondition} becomes true within {@code timeout} milliseconds.
*
- * @param uiDevice the device under test.
+ * @param uiDevice the device under test.
* @param searchCondition the condition to wait for.
*/
- public static void assertWaitForCondition(UiDevice uiDevice,
- SearchCondition<Boolean> searchCondition, long timeout) {
+ public static void assertWaitForCondition(
+ UiDevice uiDevice, SearchCondition<Boolean> searchCondition, long timeout) {
boolean result = waitForCondition(uiDevice, searchCondition, timeout);
assertTrue(searchCondition + " not true after " + timeout / 1000.0 + " seconds.", result);
}
@@ -72,52 +69,55 @@ public final class UiDeviceAsserts {
/**
* Wait until {@code searchCondition} becomes true.
*
- * @param uiDevice The device under test.
+ * @param uiDevice The device under test.
* @param searchCondition The condition to wait for.
* @return {@code true} if the condition is met, otherwise {@code false}.
*/
- public static boolean waitForCondition(UiDevice uiDevice,
- SearchCondition<Boolean> searchCondition) {
+ public static boolean waitForCondition(
+ UiDevice uiDevice, SearchCondition<Boolean> searchCondition) {
return waitForCondition(uiDevice, searchCondition, Constants.MAX_SHOW_DELAY_MILLIS);
}
- private static boolean waitForCondition(UiDevice uiDevice,
- SearchCondition<Boolean> searchCondition, long timeout) {
- long adjustedTimeout = timeout + Math.max(Constants.MIN_EXTRA_TIMEOUT,
- (long) (timeout * Constants.EXTRA_TIMEOUT_PERCENT));
+ private static boolean waitForCondition(
+ UiDevice uiDevice, SearchCondition<Boolean> searchCondition, long timeout) {
+ long adjustedTimeout =
+ timeout
+ + Math.max(
+ Constants.MIN_EXTRA_TIMEOUT,
+ (long) (timeout * Constants.EXTRA_TIMEOUT_PERCENT));
return uiDevice.wait(searchCondition, adjustedTimeout);
}
/**
* Navigates through the focus items in a container returning the container child that has a
* descendant matching the {@code selector}.
- * <p>
- * The navigation starts in the {@code direction} specified and
- * {@link Direction#reverse(Direction) reverses} once if needed. Fails if there is not a
- * focused
- * descendant, or if after completing both directions no focused child has a descendant
- * matching
+ *
+ * <p>The navigation starts in the {@code direction} specified and {@link
+ * Direction#reverse(Direction) reverses} once if needed. Fails if there is not a focused
+ * descendant, or if after completing both directions no focused child has a descendant matching
* {@code selector}.
- * <p>
- * Fails if the menu item can not be navigated to.
*
- * @param uiDevice the device under test.
+ * <p>Fails if the menu item can not be navigated to.
+ *
+ * @param uiDevice the device under test.
* @param container contains children to navigate over.
- * @param selector the selector for the object to navigate to.
+ * @param selector the selector for the object to navigate to.
* @param direction the direction to start navigating.
* @return the object navigated to.
*/
- public static UiObject2 assertNavigateTo(UiDevice uiDevice, UiObject2 container,
- BySelector selector, Direction direction) {
+ public static UiObject2 assertNavigateTo(
+ UiDevice uiDevice, UiObject2 container, BySelector selector, Direction direction) {
int count = 0;
while (count < 2) {
BySelector hasFocusedDescendant = By.hasDescendant(FOCUSED_VIEW);
UiObject2 focusedChild = null;
- SearchCondition<Boolean> untilHasFocusedDescendant = Until
- .hasObject(hasFocusedDescendant);
+ SearchCondition<Boolean> untilHasFocusedDescendant =
+ Until.hasObject(hasFocusedDescendant);
- boolean result = container.wait(untilHasFocusedDescendant,
- UiObject2Asserts.getAdjustedTimeout(Constants.MAX_SHOW_DELAY_MILLIS));
+ boolean result =
+ container.wait(
+ untilHasFocusedDescendant,
+ UiObject2Asserts.getAdjustedTimeout(Constants.MAX_SHOW_DELAY_MILLIS));
if (!result) {
// HACK: Try direction anyways because play control does not always have a
// focused item.
@@ -147,6 +147,5 @@ public final class UiDeviceAsserts {
return null;
}
- private UiDeviceAsserts() {
- }
+ private UiDeviceAsserts() {}
}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java
index 98eff906..d5545023 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java
@@ -15,21 +15,11 @@
*/
package com.android.tv.testing.uihelper;
-import static junit.framework.Assert.assertTrue;
-
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.os.Build;
-import android.os.SystemClock;
-import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiDevice;
-import android.view.InputDevice;
import android.view.KeyEvent;
-/**
- * Static utility methods for {@link UiDevice}.
- */
+/** Static utility methods for {@link UiDevice}. */
public final class UiDeviceUtils {
public static void pressDpad(UiDevice uiDevice, Direction direction) {
@@ -51,7 +41,6 @@ public final class UiDeviceUtils {
}
}
-
public static void pressKeys(UiDevice uiDevice, int... keyCodes) {
for (int k : keyCodes) {
uiDevice.pressKeyCode(k);
@@ -60,8 +49,8 @@ public final class UiDeviceUtils {
/**
* Parses the string and sends the corresponding individual key presses.
- * <p>
- * <b>Note:</b> only handles 0-9, '.', and '-'.
+ *
+ * <p><b>Note:</b> only handles 0-9, '.', and '-'.
*/
public static void pressKeys(UiDevice uiDevice, String keys) {
for (char c : keys.toCharArray()) {
@@ -77,59 +66,5 @@ public final class UiDeviceUtils {
}
}
- /**
- * Sends the DPAD Center key presses with the {@code repeat} count.
- * TODO: Remove instrumentation argument once migrated to JUnit4.
- */
- public static void pressDPadCenter(Instrumentation instrumentation, int repeat) {
- pressKey(instrumentation, KeyEvent.KEYCODE_DPAD_CENTER, repeat);
- }
-
- private static void pressKey(Instrumentation instrumentation, int keyCode, int repeat) {
- UiDevice.getInstance(instrumentation).waitForIdle();
- for (int i = 0; i < repeat; ++i) {
- assertPressKeyDown(instrumentation, keyCode, false);
- if (i < repeat - 1) {
- assertPressKeyUp(instrumentation, keyCode, false);
- }
- }
- // Send last key event synchronously.
- assertPressKeyUp(instrumentation, keyCode, true);
- }
-
- private static void assertPressKeyDown(Instrumentation instrumentation, int keyCode,
- boolean sync) {
- assertPressKey(instrumentation, KeyEvent.ACTION_DOWN, keyCode, sync);
- }
-
- private static void assertPressKeyUp(Instrumentation instrumentation, int keyCode,
- boolean sync) {
- assertPressKey(instrumentation, KeyEvent.ACTION_UP, keyCode, sync);
- }
-
- private static void assertPressKey(Instrumentation instrumentation, int action, int keyCode,
- boolean sync) {
- long eventTime = SystemClock.uptimeMillis();
- KeyEvent event = new KeyEvent(eventTime, eventTime, action, keyCode, 0, 0, -1, 0, 0,
- InputDevice.SOURCE_KEYBOARD);
- assertTrue("Failed to inject key up event:" + event,
- injectEvent(instrumentation, event, sync));
- }
-
- private static boolean injectEvent(Instrumentation instrumentation, KeyEvent event,
- boolean sync) {
- return getUiAutomation(instrumentation).injectInputEvent(event, sync);
- }
-
- private static UiAutomation getUiAutomation(Instrumentation instrumentation) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- int flags = Configurator.getInstance().getUiAutomationFlags();
- return instrumentation.getUiAutomation(flags);
- } else {
- return instrumentation.getUiAutomation();
- }
- }
-
- private UiDeviceUtils() {
- }
+ private UiDeviceUtils() {}
}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java b/tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java
index aba29f0e..ee02d7f7 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java
@@ -20,41 +20,40 @@ import static junit.framework.Assert.assertTrue;
import android.support.test.uiautomator.SearchCondition;
import android.support.test.uiautomator.UiObject2;
-/**
- * Asserts for {@link UiObject2}s.
- */
+/** Asserts for {@link UiObject2}s. */
public final class UiObject2Asserts {
/**
- * Assert that {@code searchCondition} becomes true within
- * {@value Constants#MAX_SHOW_DELAY_MILLIS} milliseconds.
+ * Assert that {@code searchCondition} becomes true within {@value
+ * Constants#MAX_SHOW_DELAY_MILLIS} milliseconds.
*
- * @param uiObject the device under test.
+ * @param uiObject the device under test.
* @param searchCondition the condition to wait for.
*/
- public static void assertWaitForCondition(UiObject2 uiObject,
- SearchCondition<Boolean> searchCondition) {
+ public static void assertWaitForCondition(
+ UiObject2 uiObject, SearchCondition<Boolean> searchCondition) {
assertWaitForCondition(uiObject, searchCondition, Constants.MAX_SHOW_DELAY_MILLIS);
}
/**
* Assert that {@code searchCondition} becomes true within {@code timeout} milliseconds.
*
- * @param uiObject the device under test.
+ * @param uiObject the device under test.
* @param searchCondition the condition to wait for.
*/
- public static void assertWaitForCondition(UiObject2 uiObject,
- SearchCondition<Boolean> searchCondition, long timeout) {
+ public static void assertWaitForCondition(
+ UiObject2 uiObject, SearchCondition<Boolean> searchCondition, long timeout) {
long adjustedTimeout = getAdjustedTimeout(timeout);
boolean result = uiObject.wait(searchCondition, adjustedTimeout);
assertTrue(searchCondition + " not true after " + timeout / 1000.0 + " seconds.", result);
}
public static long getAdjustedTimeout(long timeout) {
- return timeout + Math.max(
- Constants.MIN_EXTRA_TIMEOUT, (long) (timeout * Constants.EXTRA_TIMEOUT_PERCENT));
+ return timeout
+ + Math.max(
+ Constants.MIN_EXTRA_TIMEOUT,
+ (long) (timeout * Constants.EXTRA_TIMEOUT_PERCENT));
}
- private UiObject2Asserts() {
- }
+ private UiObject2Asserts() {}
}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java b/tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java
index 2a997a67..2f3779c5 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java
@@ -19,9 +19,7 @@ import android.graphics.Point;
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiObject2;
-/**
- * Static utility methods for {@link UiObject2}s.
- */
+/** Static utility methods for {@link UiObject2}s. */
public class UiObject2Utils {
public static boolean hasSiblingInDirection(UiObject2 theUiObject, Direction direction) {
@@ -56,6 +54,5 @@ public class UiObject2Utils {
return false;
}
- private UiObject2Utils() {
- }
+ private UiObject2Utils() {}
}
diff --git a/tests/common/src/com/android/tv/testing/utils/TestUtils.java b/tests/common/src/com/android/tv/testing/utils/TestUtils.java
new file mode 100644
index 00000000..6604c9ad
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/utils/TestUtils.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2015 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.testing.utils;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Icon;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.tv.TvInputInfo;
+import android.os.Build;
+import android.os.Bundle;
+import java.lang.reflect.Constructor;
+
+/** A class that includes convenience methods for testing. */
+public class TestUtils {
+ /** Creates a {@link TvInputInfo}. */
+ public static TvInputInfo createTvInputInfo(
+ ResolveInfo service, String id, String parentId, int type, boolean isHardwareInput)
+ throws Exception {
+ return createTvInputInfo(service, id, parentId, type, isHardwareInput, false, 0);
+ }
+
+ /**
+ * Creates a {@link TvInputInfo}.
+ *
+ * <p>If this is called on MNC, {@code canRecord} and {@code tunerCount} are ignored.
+ */
+ public static TvInputInfo createTvInputInfo(
+ ResolveInfo service,
+ String id,
+ String parentId,
+ int type,
+ boolean isHardwareInput,
+ boolean canRecord,
+ int tunerCount)
+ throws Exception {
+ // Create a mock TvInputInfo by using private constructor
+ // Note that mockito doesn't support mock/spy on final object.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ return createTvInputInfoForO(
+ service, id, parentId, type, isHardwareInput, canRecord, tunerCount);
+
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return createTvInputInfoForNyc(
+ service, id, parentId, type, isHardwareInput, canRecord, tunerCount);
+ }
+ return createTvInputInfoForMnc(service, id, parentId, type, isHardwareInput);
+ }
+
+ /**
+ * private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput,
+ * CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
+ * String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo,
+ * boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) {
+ */
+ private static TvInputInfo createTvInputInfoForO(
+ ResolveInfo service,
+ String id,
+ String parentId,
+ int type,
+ boolean isHardwareInput,
+ boolean canRecord,
+ int tunerCount)
+ throws Exception {
+ Constructor<TvInputInfo> constructor =
+ TvInputInfo.class.getDeclaredConstructor(
+ ResolveInfo.class,
+ String.class,
+ int.class,
+ boolean.class,
+ CharSequence.class,
+ int.class,
+ Icon.class,
+ Icon.class,
+ Icon.class,
+ String.class,
+ boolean.class,
+ int.class,
+ HdmiDeviceInfo.class,
+ boolean.class,
+ String.class,
+ Bundle.class);
+ constructor.setAccessible(true);
+ return constructor.newInstance(
+ service,
+ id,
+ type,
+ isHardwareInput,
+ null,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ canRecord,
+ tunerCount,
+ null,
+ false,
+ parentId,
+ null);
+ }
+
+ /**
+ * private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput,
+ * CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
+ * String setupActivity, String settingsActivity, boolean canRecord, int tunerCount,
+ * HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch, String parentId, Bundle
+ * extras) {
+ */
+ private static TvInputInfo createTvInputInfoForNyc(
+ ResolveInfo service,
+ String id,
+ String parentId,
+ int type,
+ boolean isHardwareInput,
+ boolean canRecord,
+ int tunerCount)
+ throws Exception {
+ Constructor<TvInputInfo> constructor =
+ TvInputInfo.class.getDeclaredConstructor(
+ ResolveInfo.class,
+ String.class,
+ int.class,
+ boolean.class,
+ CharSequence.class,
+ int.class,
+ Icon.class,
+ Icon.class,
+ Icon.class,
+ String.class,
+ String.class,
+ boolean.class,
+ int.class,
+ HdmiDeviceInfo.class,
+ boolean.class,
+ String.class,
+ Bundle.class);
+ constructor.setAccessible(true);
+ return constructor.newInstance(
+ service,
+ id,
+ type,
+ isHardwareInput,
+ null,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ canRecord,
+ tunerCount,
+ null,
+ false,
+ parentId,
+ null);
+ }
+
+ private static TvInputInfo createTvInputInfoForMnc(
+ ResolveInfo service, String id, String parentId, int type, boolean isHardwareInput)
+ throws Exception {
+ Constructor<TvInputInfo> constructor =
+ TvInputInfo.class.getDeclaredConstructor(
+ ResolveInfo.class, String.class, String.class, int.class, boolean.class);
+ constructor.setAccessible(true);
+ return constructor.newInstance(service, id, parentId, type, isHardwareInput);
+ }
+
+ public static ResolveInfo createResolveInfo(String packageName, String name) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = new ServiceInfo();
+ resolveInfo.serviceInfo.packageName = packageName;
+ resolveInfo.serviceInfo.name = name;
+ resolveInfo.serviceInfo.metaData = new Bundle();
+ resolveInfo.serviceInfo.applicationInfo = new ApplicationInfo();
+ return resolveInfo;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/Utils.java b/tests/common/src/com/android/tv/testing/utils/Utils.java
index b2b4036e..a116db0b 100644
--- a/tests/common/src/com/android/tv/testing/Utils.java
+++ b/tests/common/src/com/android/tv/testing/utils/Utils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.testing;
+package com.android.tv.testing.utils;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -26,9 +26,8 @@ import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.net.Uri;
import android.util.Log;
-
-import com.android.tv.common.TvCommonUtils;
-
+import com.android.tv.common.CommonConstants;
+import com.android.tv.common.util.CommonUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -40,12 +39,10 @@ import java.util.Random;
/**
* An utility class for testing.
*
- * <p>This class is also used to check whether TV app is running in tests or not.
- *
- * @see TvCommonUtils#isRunningInTest
+ * @see CommonUtils#isRunningInTest
*/
public final class Utils {
- private static final String TAG ="Utils";
+ private static final String TAG = "Utils";
private static final long DEFAULT_RANDOM_SEED = getSeed();
@@ -55,10 +52,12 @@ public final class Utils {
}
Resources res = context.getResources();
return new Uri.Builder()
- .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
- .authority(res.getResourcePackageName(resId))
- .path(res.getResourceTypeName(resId))
- .appendPath(res.getResourceEntryName(resId)).build().toString();
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(res.getResourcePackageName(resId))
+ .path(res.getResourceTypeName(resId))
+ .appendPath(res.getResourceEntryName(resId))
+ .build()
+ .toString();
}
public static void copy(InputStream is, OutputStream os) throws IOException {
@@ -91,8 +90,8 @@ public final class Utils {
}
/**
- * Return the Random class which is needed to make random data for testing.
- * Default seed of the random is today's date.
+ * Return the Random class which is needed to make random data for testing. Default seed of the
+ * random is today's date.
*/
public static Random createTestRandom() {
return new Random(DEFAULT_RANDOM_SEED);
@@ -106,17 +105,15 @@ public final class Utils {
return Long.valueOf(today);
}
- private Utils() {}
-
- /**
- * Checks whether TvActivity is enabled or not.
- */
+ /** Checks whether TvActivity is enabled or not. */
public static boolean isTvActivityEnabled(Context context) {
PackageManager pm = context.getPackageManager();
- ComponentName name = new ComponentName("com.android.tv",
- "com.android.tv.TvActivity");
+ ComponentName name =
+ new ComponentName(CommonConstants.BASE_PACKAGE, "com.android.tv.TvActivity");
int enabled = pm.getComponentEnabledSetting(name);
return enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
}
+
+ private Utils() {}
}