aboutsummaryrefslogtreecommitdiff
path: root/tests/common/src/com/android/tv/testing/data
diff options
context:
space:
mode:
Diffstat (limited to 'tests/common/src/com/android/tv/testing/data')
-rw-r--r--tests/common/src/com/android/tv/testing/data/ChannelInfo.java352
-rw-r--r--tests/common/src/com/android/tv/testing/data/ChannelUtils.java189
-rw-r--r--tests/common/src/com/android/tv/testing/data/ProgramInfo.java336
-rw-r--r--tests/common/src/com/android/tv/testing/data/ProgramUtils.java147
4 files changed, 1024 insertions, 0 deletions
diff --git a/tests/common/src/com/android/tv/testing/data/ChannelInfo.java b/tests/common/src/com/android/tv/testing/data/ChannelInfo.java
new file mode 100644
index 00000000..e39c057d
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/data/ChannelInfo.java
@@ -0,0 +1,352 @@
+/*
+ * 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.data;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import android.util.SparseArray;
+import java.util.Objects;
+
+/** 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);
+ VIDEO_HEIGHT_TO_FORMAT_MAP.put(720, TvContract.Channels.VIDEO_FORMAT_720P);
+ VIDEO_HEIGHT_TO_FORMAT_MAP.put(1080, TvContract.Channels.VIDEO_FORMAT_1080P);
+ VIDEO_HEIGHT_TO_FORMAT_MAP.put(2160, TvContract.Channels.VIDEO_FORMAT_2160P);
+ VIDEO_HEIGHT_TO_FORMAT_MAP.put(4320, TvContract.Channels.VIDEO_FORMAT_4320P);
+ }
+
+ public static final String[] PROJECTION = {
+ TvContract.Channels.COLUMN_DISPLAY_NUMBER,
+ TvContract.Channels.COLUMN_DISPLAY_NAME,
+ TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID,
+ };
+
+ public final String number;
+ public final String name;
+ public final String logoUrl;
+ public final int originalNetworkId;
+ public final int videoWidth;
+ public final int videoHeight;
+ public final float videoPixelAspectRatio;
+ public final int audioChannel;
+ public final int audioLanguageCount;
+ public final boolean hasClosedCaption;
+ public final ProgramInfo program;
+ public final String appLinkText;
+ public final int appLinkColor;
+ public final String appLinkIconUri;
+ public final String appLinkPosterArtUri;
+ public final String appLinkIntentUri;
+
+ /**
+ * 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.
+ */
+ public static ChannelInfo create(@Nullable Context context, int 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));
+ }
+ return builder.build();
+ }
+
+ public static String getUriStringForChannelLogo(Context context, int logoIndex) {
+ int index = (logoIndex % 1000) + 1;
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(context.getPackageName())
+ .path("drawable")
+ .appendPath("ch_" + index + "_logo")
+ .build()
+ .toString();
+ }
+
+ public static ChannelInfo fromCursor(Cursor c) {
+ // TODO: Fill other fields.
+ Builder builder = new Builder();
+ int index = c.getColumnIndex(TvContract.Channels.COLUMN_DISPLAY_NUMBER);
+ if (index >= 0) {
+ builder.setNumber(c.getString(index));
+ }
+ index = c.getColumnIndex(TvContract.Channels.COLUMN_DISPLAY_NAME);
+ if (index >= 0) {
+ builder.setName(c.getString(index));
+ }
+ index = c.getColumnIndex(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID);
+ if (index >= 0) {
+ builder.setOriginalNetworkId(c.getInt(index));
+ }
+ 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,
+ String appLinkIntentUri) {
+ this.number = number;
+ this.name = name;
+ this.logoUrl = logoUrl;
+ this.originalNetworkId = originalNetworkId;
+ this.videoWidth = videoWidth;
+ this.videoHeight = videoHeight;
+ this.videoPixelAspectRatio = videoPixelAspectRatio;
+ this.audioChannel = audioChannel;
+ this.audioLanguageCount = audioLanguageCount;
+ this.hasClosedCaption = hasClosedCaption;
+ this.program = program;
+ this.appLinkText = appLinkText;
+ this.appLinkColor = appLinkColor;
+ this.appLinkIconUri = appLinkIconUri;
+ this.appLinkPosterArtUri = appLinkPosterArtUri;
+ this.appLinkIntentUri = appLinkIntentUri;
+ }
+
+ public String getVideoFormat() {
+ return VIDEO_HEIGHT_TO_FORMAT_MAP.get(videoHeight);
+ }
+
+ @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
+ + "}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ 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);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(number, name, originalNetworkId);
+ }
+
+ /** 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 mAudioChannel;
+ private int mAudioLanguageCount;
+ private boolean mHasClosedCaption;
+ private ProgramInfo mProgram;
+ private String mAppLinkText;
+ private int mAppLinkColor;
+ private String mAppLinkIconUri;
+ private String mAppLinkPosterArtUri;
+ private String mAppLinkIntentUri;
+
+ public Builder() {}
+
+ public Builder(ChannelInfo other) {
+ mNumber = other.number;
+ mName = other.name;
+ mLogoUrl = other.name;
+ mOriginalNetworkId = other.originalNetworkId;
+ mVideoWidth = other.videoWidth;
+ mVideoHeight = other.videoHeight;
+ mVideoPixelAspectRatio = other.videoPixelAspectRatio;
+ mAudioChannel = other.audioChannel;
+ mAudioLanguageCount = other.audioLanguageCount;
+ mHasClosedCaption = other.hasClosedCaption;
+ mProgram = other.program;
+ }
+
+ public Builder setName(String name) {
+ mName = name;
+ return this;
+ }
+
+ public Builder setNumber(String number) {
+ mNumber = number;
+ return this;
+ }
+
+ public Builder setLogoUrl(String logoUrl) {
+ mLogoUrl = logoUrl;
+ return this;
+ }
+
+ public Builder setOriginalNetworkId(int originalNetworkId) {
+ mOriginalNetworkId = originalNetworkId;
+ return this;
+ }
+
+ public Builder setVideoWidth(int videoWidth) {
+ mVideoWidth = videoWidth;
+ return this;
+ }
+
+ public Builder setVideoHeight(int videoHeight) {
+ mVideoHeight = videoHeight;
+ return this;
+ }
+
+ public Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
+ mVideoPixelAspectRatio = videoPixelAspectRatio;
+ return this;
+ }
+
+ public Builder setAudioChannel(int audioChannel) {
+ mAudioChannel = audioChannel;
+ return this;
+ }
+
+ public Builder setAudioLanguageCount(int audioLanguageCount) {
+ mAudioLanguageCount = audioLanguageCount;
+ return this;
+ }
+
+ public Builder setHasClosedCaption(boolean hasClosedCaption) {
+ mHasClosedCaption = hasClosedCaption;
+ return this;
+ }
+
+ public Builder setProgram(ProgramInfo program) {
+ mProgram = program;
+ return this;
+ }
+
+ public Builder setAppLinkText(String appLinkText) {
+ mAppLinkText = appLinkText;
+ return this;
+ }
+
+ public Builder setAppLinkColor(int appLinkColor) {
+ mAppLinkColor = appLinkColor;
+ return this;
+ }
+
+ public Builder setAppLinkIconUri(String appLinkIconUri) {
+ mAppLinkIconUri = appLinkIconUri;
+ return this;
+ }
+
+ public Builder setAppLinkPosterArtUri(String appLinkPosterArtUri) {
+ mAppLinkPosterArtUri = appLinkPosterArtUri;
+ return this;
+ }
+
+ public Builder setAppLinkIntentUri(String appLinkIntentUri) {
+ mAppLinkIntentUri = appLinkIntentUri;
+ return this;
+ }
+
+ public ChannelInfo build() {
+ 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/data/ChannelUtils.java b/tests/common/src/com/android/tv/testing/data/ChannelUtils.java
new file mode 100644
index 00000000..920c7087
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/data/ChannelUtils.java
@@ -0,0 +1,189 @@
+/*
+ * 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.data;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.media.tv.TvContract.Channels;
+import android.net.Uri;
+import android.os.AsyncTask;
+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}. */
+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
+ * com.android.tv.testing.data.ChannelInfo#fromCursor(Cursor)}.
+ */
+ @WorkerThread
+ public static Map<Long, ChannelInfo> queryChannelInfoMapForTvInput(
+ Context context, String inputId) {
+ Uri uri = TvContract.buildChannelsUriForInput(inputId);
+ Map<Long, ChannelInfo> map = new HashMap<>();
+
+ 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)) {
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ map.put(cursor.getLong(0), ChannelInfo.fromCursor(cursor));
+ }
+ }
+ return map;
+ }
+ }
+
+ @WorkerThread
+ public static void updateChannels(Context context, String inputId, List<ChannelInfo> channels) {
+ // Create a map from original network ID to channel row ID for existing channels.
+ SparseArray<Long> existingChannelsMap = new SparseArray<>();
+ Uri channelsUri = TvContract.buildChannelsUriForInput(inputId);
+ String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID};
+ ContentResolver resolver = context.getContentResolver();
+ try (Cursor cursor = resolver.query(channelsUri, projection, null, null, null)) {
+ while (cursor != null && cursor.moveToNext()) {
+ long rowId = cursor.getLong(0);
+ int originalNetworkId = cursor.getInt(1);
+ existingChannelsMap.put(originalNetworkId, rowId);
+ }
+ }
+
+ Map<Uri, String> logos = new HashMap<>();
+ for (ChannelInfo channel : channels) {
+ // If a channel exists, update it. If not, insert a new one.
+ ContentValues values = new ContentValues();
+ values.put(Channels.COLUMN_INPUT_ID, inputId);
+ values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number);
+ values.put(Channels.COLUMN_DISPLAY_NAME, channel.name);
+ values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId);
+ String videoFormat = channel.getVideoFormat();
+ if (videoFormat != null) {
+ values.put(Channels.COLUMN_VIDEO_FORMAT, videoFormat);
+ } else {
+ values.putNull(Channels.COLUMN_VIDEO_FORMAT);
+ }
+ if (!TextUtils.isEmpty(channel.appLinkText)) {
+ values.put(Channels.COLUMN_APP_LINK_TEXT, channel.appLinkText);
+ }
+ if (channel.appLinkColor != 0) {
+ values.put(Channels.COLUMN_APP_LINK_COLOR, channel.appLinkColor);
+ }
+ if (!TextUtils.isEmpty(channel.appLinkPosterArtUri)) {
+ values.put(Channels.COLUMN_APP_LINK_POSTER_ART_URI, channel.appLinkPosterArtUri);
+ }
+ if (!TextUtils.isEmpty(channel.appLinkIconUri)) {
+ values.put(Channels.COLUMN_APP_LINK_ICON_URI, channel.appLinkIconUri);
+ }
+ if (!TextUtils.isEmpty(channel.appLinkIntentUri)) {
+ values.put(Channels.COLUMN_APP_LINK_INTENT_URI, channel.appLinkIntentUri);
+ }
+ Long rowId = existingChannelsMap.get(channel.originalNetworkId);
+ Uri uri;
+ if (rowId == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Inserting " + channel);
+ }
+ uri = resolver.insert(TvContract.Channels.CONTENT_URI, values);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Updating " + channel);
+ }
+ uri = TvContract.buildChannelUri(rowId);
+ resolver.update(uri, values, null, null);
+ existingChannelsMap.remove(channel.originalNetworkId);
+ }
+ if (!TextUtils.isEmpty(channel.logoUrl)) {
+ logos.put(TvContract.buildChannelLogoUri(uri), channel.logoUrl);
+ }
+ }
+ if (!logos.isEmpty()) {
+ new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos);
+ }
+
+ // Deletes channels which don't exist in the new feed.
+ int size = existingChannelsMap.size();
+ for (int i = 0; i < size; ++i) {
+ Long rowId = existingChannelsMap.valueAt(i);
+ resolver.delete(TvContract.buildChannelUri(rowId), null, null);
+ }
+ }
+
+ public static void copy(InputStream is, OutputStream os) throws IOException {
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = is.read(buffer)) != -1) {
+ os.write(buffer, 0, len);
+ }
+ }
+
+ private 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;
+
+ InsertLogosTask(Context context) {
+ mContext = context;
+ }
+
+ @SafeVarargs
+ @Override
+ public final Void doInBackground(Map<Uri, String>... logosList) {
+ for (Map<Uri, String> logos : logosList) {
+ for (Uri uri : logos.keySet()) {
+ if (uri == null) {
+ continue;
+ }
+ Uri logoUri = Uri.parse(logos.get(uri));
+ try (InputStream is = mContext.getContentResolver().openInputStream(logoUri);
+ OutputStream os = mContext.getContentResolver().openOutputStream(uri)) {
+ copy(is, os);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Failed to write " + logoUri + " to " + uri, ioe);
+ }
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/data/ProgramInfo.java b/tests/common/src/com/android/tv/testing/data/ProgramInfo.java
new file mode 100644
index 00000000..6d801425
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/data/ProgramInfo.java
@@ -0,0 +1,336 @@
+/*
+ * 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.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. */
+ 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.
+ */
+ 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.
+ */
+ 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
+ };
+
+ /**
+ * 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 durationsSumMs;
+
+ static {
+ durationsSumMs = 0;
+ for (long duration : DURATIONS_MS) {
+ durationsSumMs += duration;
+ }
+ }
+
+ /** 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
+ };
+
+ public final String title;
+ public final String episode;
+ public final int seasonNumber;
+ public final int episodeNumber;
+ public final String posterArtUri;
+ public final String description;
+ public final long durationMs;
+ public final String genre;
+ public final TvContentRating[] contentRatings;
+ public final String resourceUri;
+
+ public static ProgramInfo fromCursor(Cursor c) {
+ // TODO: Fill other fields.
+ Builder builder = new Builder();
+ int index = c.getColumnIndex(TvContract.Programs.COLUMN_TITLE);
+ if (index >= 0) {
+ builder.setTitle(c.getString(index));
+ }
+ index = c.getColumnIndex(TvContract.Programs.COLUMN_SHORT_DESCRIPTION);
+ if (index >= 0) {
+ builder.setDescription(c.getString(index));
+ }
+ index = c.getColumnIndex(TvContract.Programs.COLUMN_EPISODE_TITLE);
+ if (index >= 0) {
+ builder.setEpisode(c.getString(index));
+ }
+ 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) {
+ this.title = title;
+ this.episode = episode;
+ this.seasonNumber = seasonNumber;
+ this.episodeNumber = episodeNumber;
+ this.posterArtUri = posterArtUri;
+ this.description = description;
+ this.durationMs = durationMs;
+ this.contentRatings = contentRatings;
+ this.genre = genre;
+ this.resourceUri = resourceUri;
+ }
+
+ /**
+ * Create a instance of {@link ProgramInfo} whose content will be generated as much as possible.
+ */
+ public static ProgramInfo create() {
+ return new Builder().build();
+ }
+
+ /**
+ * 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.
+ */
+ public int getIndex(long timeMs, long channelId) {
+ if (durationMs != GEN_DURATION) {
+ return Math.max((int) (timeMs / durationMs), 0);
+ }
+ long startTimeMs = channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))];
+ 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++;
+ }
+ return index;
+ }
+
+ /**
+ * 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) * durationsSumMs;
+ for (int i = 0; i < index % DURATIONS_MS.length; i++) {
+ startTimeMs += DURATIONS_MS[i];
+ }
+ return startTimeMs;
+ }
+
+ /**
+ * 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) {
+ if (!GEN_TITLE.equals(title)
+ && episode == null
+ && !GEN_POSTER.equals(posterArtUri)
+ && durationMs != GEN_DURATION
+ && !GEN_GENRE.equals(genre)) {
+ return this;
+ }
+ return new ProgramInfo(
+ GEN_TITLE.equals(title) ? "Title(" + index + ")" : title,
+ GEN_EPISODE.equals(episode) ? "Episode(" + index + ")" : episode,
+ 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])
+ : posterArtUri,
+ description,
+ durationMs == GEN_DURATION ? DURATIONS_MS[index % DURATIONS_MS.length] : durationMs,
+ contentRatings,
+ GEN_GENRE.equals(genre) ? GENRES[index % GENRES.length] : genre,
+ resourceUri);
+ }
+
+ @Override
+ public String toString() {
+ return "ProgramInfo{title="
+ + title
+ + ", episode="
+ + episode
+ + ", durationMs="
+ + durationMs
+ + "}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ 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)
+ && Arrays.equals(contentRatings, that.contentRatings)
+ && Objects.equals(resourceUri, that.resourceUri);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(title, episode, seasonNumber, episodeNumber);
+ }
+
+ public static class Builder {
+ private String mTitle = GEN_TITLE;
+ private String mEpisode = GEN_EPISODE;
+ private int mSeasonNumber;
+ private int mEpisodeNumber;
+ private String mPosterArtUri = GEN_POSTER;
+ private String mDescription;
+ private long mDurationMs = GEN_DURATION;
+ private TvContentRating[] mContentRatings;
+ private String mGenre = GEN_GENRE;
+ private String mResourceUri;
+
+ public Builder setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ public Builder setEpisode(String episode) {
+ mEpisode = episode;
+ return this;
+ }
+
+ public Builder setSeasonNumber(int seasonNumber) {
+ mSeasonNumber = seasonNumber;
+ return this;
+ }
+
+ public Builder setEpisodeNumber(int episodeNumber) {
+ mEpisodeNumber = episodeNumber;
+ return this;
+ }
+
+ public Builder setPosterArtUri(String posterArtUri) {
+ mPosterArtUri = posterArtUri;
+ return this;
+ }
+
+ public Builder setDescription(String description) {
+ mDescription = description;
+ return this;
+ }
+
+ public Builder setDurationMs(long durationMs) {
+ mDurationMs = durationMs;
+ return this;
+ }
+
+ public Builder setContentRatings(TvContentRating[] contentRatings) {
+ mContentRatings = contentRatings;
+ return this;
+ }
+
+ public Builder setGenre(String genre) {
+ mGenre = genre;
+ return this;
+ }
+
+ public Builder setResourceUri(String resourceUri) {
+ mResourceUri = resourceUri;
+ return this;
+ }
+
+ public ProgramInfo build() {
+ return new ProgramInfo(
+ mTitle,
+ mEpisode,
+ mSeasonNumber,
+ mEpisodeNumber,
+ mPosterArtUri,
+ mDescription,
+ mDurationMs,
+ mContentRatings,
+ mGenre,
+ mResourceUri);
+ }
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/data/ProgramUtils.java b/tests/common/src/com/android/tv/testing/data/ProgramUtils.java
new file mode 100644
index 00000000..21647719
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/data/ProgramUtils.java
@@ -0,0 +1,147 @@
+/*
+ * 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.data;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+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;
+
+/** 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 */
+ 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.
+ */
+ 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,
+ TvContentRatingCache.contentRatingsToString(program.contentRatings));
+
+ long timeMs = getLastProgramEndTimeMs(context, channelUri, currentTimeMs, targetEndTimeMs);
+ if (timeMs <= 0) {
+ timeMs = currentTimeMs;
+ }
+ int index = program.getIndex(timeMs, channelId);
+ timeMs = program.getStartTimeMs(index, channelId);
+
+ ArrayList<ContentValues> list = new ArrayList<>();
+ while (timeMs < targetEndTimeMs) {
+ ProgramInfo programAt = program.build(context, index++);
+ values.put(Programs.COLUMN_TITLE, programAt.title);
+ values.put(Programs.COLUMN_EPISODE_TITLE, programAt.episode);
+ if (programAt.seasonNumber != 0) {
+ values.put(Programs.COLUMN_SEASON_NUMBER, programAt.seasonNumber);
+ }
+ if (programAt.episodeNumber != 0) {
+ values.put(Programs.COLUMN_EPISODE_NUMBER, programAt.episodeNumber);
+ }
+ values.put(Programs.COLUMN_POSTER_ART_URI, programAt.posterArtUri);
+ values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, timeMs);
+ values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, timeMs + programAt.durationMs);
+ values.put(Programs.COLUMN_CANONICAL_GENRE, programAt.genre);
+ values.put(Programs.COLUMN_POSTER_ART_URI, programAt.posterArtUri);
+ list.add(new ContentValues(values));
+ timeMs += programAt.durationMs;
+
+ if (list.size() >= MAX_DB_INSERT_COUNT_AT_ONCE || timeMs >= targetEndTimeMs) {
+ try {
+ context.getContentResolver()
+ .bulkInsert(
+ Programs.CONTENT_URI,
+ list.toArray(new ContentValues[list.size()]));
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Can't insert EPG.", e);
+ return;
+ }
+ if (DEBUG) Log.d(TAG, "Inserted " + list.size() + " programs for " + channelUri);
+ list.clear();
+ }
+ }
+ }
+
+ private static long getLastProgramEndTimeMs(
+ Context context, Uri channelUri, long startTimeMs, long endTimeMs) {
+ Uri uri = TvContract.buildProgramsUriForChannel(channelUri, startTimeMs, endTimeMs);
+ String[] projection = {Programs.COLUMN_END_TIME_UTC_MILLIS};
+ try (Cursor cursor =
+ context.getContentResolver().query(uri, projection, null, null, null)) {
+ if (cursor != null && cursor.moveToLast()) {
+ return cursor.getLong(0);
+ }
+ }
+ return 0;
+ }
+
+ 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);
+ }
+ }
+}