aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/dvr/data
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/dvr/data')
-rw-r--r--src/com/android/tv/dvr/data/IdGenerator.java50
-rw-r--r--src/com/android/tv/dvr/data/RecordedProgram.java868
-rw-r--r--src/com/android/tv/dvr/data/ScheduledRecording.java902
-rw-r--r--src/com/android/tv/dvr/data/SeasonEpisodeNumber.java72
-rw-r--r--src/com/android/tv/dvr/data/SeriesInfo.java76
-rw-r--r--src/com/android/tv/dvr/data/SeriesRecording.java756
6 files changed, 2724 insertions, 0 deletions
diff --git a/src/com/android/tv/dvr/data/IdGenerator.java b/src/com/android/tv/dvr/data/IdGenerator.java
new file mode 100644
index 00000000..2ade1dad
--- /dev/null
+++ b/src/com/android/tv/dvr/data/IdGenerator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.dvr.data;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A class which generate the ID which increases sequentially.
+ */
+public class IdGenerator {
+ /**
+ * ID generator for the scheduled recording.
+ */
+ public static final IdGenerator SCHEDULED_RECORDING = new IdGenerator();
+
+ /**
+ * ID generator for the series recording.
+ */
+ public static final IdGenerator SERIES_RECORDING = new IdGenerator();
+
+ private final AtomicLong mMaxId = new AtomicLong(0);
+
+ /**
+ * Sets the new maximum ID.
+ */
+ public void setMaxId(long maxId) {
+ mMaxId.set(maxId);
+ }
+
+ /**
+ * Returns the new ID which is greater than the existing maximum ID by 1.
+ */
+ public long newId() {
+ return mMaxId.incrementAndGet();
+ }
+}
diff --git a/src/com/android/tv/dvr/data/RecordedProgram.java b/src/com/android/tv/dvr/data/RecordedProgram.java
new file mode 100644
index 00000000..18e1c769
--- /dev/null
+++ b/src/com/android/tv/dvr/data/RecordedProgram.java
@@ -0,0 +1,868 @@
+/*
+ * Copyright (C) 2016 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.dvr.data;
+
+import static android.media.tv.TvContract.RecordedPrograms;
+
+import android.annotation.TargetApi;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.tv.common.R;
+import com.android.tv.data.BaseProgram;
+import com.android.tv.data.GenreItems;
+import com.android.tv.data.InternalDataUtils;
+import com.android.tv.util.Utils;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class RecordedProgram extends BaseProgram {
+ public static final int ID_NOT_SET = -1;
+
+ public final static String[] PROJECTION = {
+ // These are in exactly the order listed in RecordedPrograms
+ RecordedPrograms._ID,
+ RecordedPrograms.COLUMN_PACKAGE_NAME,
+ RecordedPrograms.COLUMN_INPUT_ID,
+ RecordedPrograms.COLUMN_CHANNEL_ID,
+ RecordedPrograms.COLUMN_TITLE,
+ RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
+ RecordedPrograms.COLUMN_SEASON_TITLE,
+ RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
+ RecordedPrograms.COLUMN_EPISODE_TITLE,
+ RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_BROADCAST_GENRE,
+ RecordedPrograms.COLUMN_CANONICAL_GENRE,
+ RecordedPrograms.COLUMN_SHORT_DESCRIPTION,
+ RecordedPrograms.COLUMN_LONG_DESCRIPTION,
+ RecordedPrograms.COLUMN_VIDEO_WIDTH,
+ RecordedPrograms.COLUMN_VIDEO_HEIGHT,
+ RecordedPrograms.COLUMN_AUDIO_LANGUAGE,
+ RecordedPrograms.COLUMN_CONTENT_RATING,
+ RecordedPrograms.COLUMN_POSTER_ART_URI,
+ RecordedPrograms.COLUMN_THUMBNAIL_URI,
+ RecordedPrograms.COLUMN_SEARCHABLE,
+ RecordedPrograms.COLUMN_RECORDING_DATA_URI,
+ RecordedPrograms.COLUMN_RECORDING_DATA_BYTES,
+ RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
+ RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ RecordedPrograms.COLUMN_VERSION_NUMBER,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ };
+
+ public static RecordedProgram fromCursor(Cursor cursor) {
+ int index = 0;
+ Builder builder = builder()
+ .setId(cursor.getLong(index++))
+ .setPackageName(cursor.getString(index++))
+ .setInputId(cursor.getString(index++))
+ .setChannelId(cursor.getLong(index++))
+ .setTitle(cursor.getString(index++))
+ .setSeasonNumber(cursor.getString(index++))
+ .setSeasonTitle(cursor.getString(index++))
+ .setEpisodeNumber(cursor.getString(index++))
+ .setEpisodeTitle(cursor.getString(index++))
+ .setStartTimeUtcMillis(cursor.getLong(index++))
+ .setEndTimeUtcMillis(cursor.getLong(index++))
+ .setBroadcastGenres(cursor.getString(index++))
+ .setCanonicalGenres(cursor.getString(index++))
+ .setShortDescription(cursor.getString(index++))
+ .setLongDescription(cursor.getString(index++))
+ .setVideoWidth(cursor.getInt(index++))
+ .setVideoHeight(cursor.getInt(index++))
+ .setAudioLanguage(cursor.getString(index++))
+ .setContentRating(cursor.getString(index++))
+ .setPosterArtUri(cursor.getString(index++))
+ .setThumbnailUri(cursor.getString(index++))
+ .setSearchable(cursor.getInt(index++) == 1)
+ .setDataUri(cursor.getString(index++))
+ .setDataBytes(cursor.getLong(index++))
+ .setDurationMillis(cursor.getLong(index++))
+ .setExpireTimeUtcMillis(cursor.getLong(index++))
+ .setInternalProviderFlag1(cursor.getInt(index++))
+ .setInternalProviderFlag2(cursor.getInt(index++))
+ .setInternalProviderFlag3(cursor.getInt(index++))
+ .setInternalProviderFlag4(cursor.getInt(index++))
+ .setVersionNumber(cursor.getInt(index++));
+ if (Utils.isInBundledPackageSet(builder.mPackageName)) {
+ InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder);
+ }
+ return builder.build();
+ }
+
+ public static ContentValues toValues(RecordedProgram recordedProgram) {
+ ContentValues values = new ContentValues();
+ if (recordedProgram.mId != ID_NOT_SET) {
+ values.put(RecordedPrograms._ID, recordedProgram.mId);
+ }
+ values.put(RecordedPrograms.COLUMN_INPUT_ID, recordedProgram.mInputId);
+ values.put(RecordedPrograms.COLUMN_CHANNEL_ID, recordedProgram.mChannelId);
+ values.put(RecordedPrograms.COLUMN_TITLE, recordedProgram.mTitle);
+ values.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, recordedProgram.mSeasonNumber);
+ values.put(RecordedPrograms.COLUMN_SEASON_TITLE, recordedProgram.mSeasonTitle);
+ values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, recordedProgram.mEpisodeNumber);
+ values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, recordedProgram.mTitle);
+ values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ recordedProgram.mStartTimeUtcMillis);
+ values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, recordedProgram.mEndTimeUtcMillis);
+ values.put(RecordedPrograms.COLUMN_BROADCAST_GENRE,
+ safeEncode(recordedProgram.mBroadcastGenres));
+ values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE,
+ safeEncode(recordedProgram.mCanonicalGenres));
+ values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, recordedProgram.mShortDescription);
+ values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, recordedProgram.mLongDescription);
+ if (recordedProgram.mVideoWidth == 0) {
+ values.putNull(RecordedPrograms.COLUMN_VIDEO_WIDTH);
+ } else {
+ values.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, recordedProgram.mVideoWidth);
+ }
+ if (recordedProgram.mVideoHeight == 0) {
+ values.putNull(RecordedPrograms.COLUMN_VIDEO_HEIGHT);
+ } else {
+ values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, recordedProgram.mVideoHeight);
+ }
+ values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, recordedProgram.mAudioLanguage);
+ values.put(RecordedPrograms.COLUMN_CONTENT_RATING, recordedProgram.mContentRating);
+ values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, recordedProgram.mPosterArtUri);
+ values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, recordedProgram.mThumbnailUri);
+ values.put(RecordedPrograms.COLUMN_SEARCHABLE, recordedProgram.mSearchable ? 1 : 0);
+ values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI,
+ safeToString(recordedProgram.mDataUri));
+ values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, recordedProgram.mDataBytes);
+ values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
+ recordedProgram.mDurationMillis);
+ values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
+ recordedProgram.mExpireTimeUtcMillis);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ InternalDataUtils.serializeInternalProviderData(recordedProgram));
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ recordedProgram.mInternalProviderFlag1);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ recordedProgram.mInternalProviderFlag2);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ recordedProgram.mInternalProviderFlag3);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ recordedProgram.mInternalProviderFlag4);
+ values.put(RecordedPrograms.COLUMN_VERSION_NUMBER, recordedProgram.mVersionNumber);
+ return values;
+ }
+
+ public static class Builder{
+ private long mId = ID_NOT_SET;
+ private String mPackageName;
+ private String mInputId;
+ private long mChannelId;
+ private String mTitle;
+ private String mSeriesId;
+ private String mSeasonNumber;
+ private String mSeasonTitle;
+ private String mEpisodeNumber;
+ private String mEpisodeTitle;
+ private long mStartTimeUtcMillis;
+ private long mEndTimeUtcMillis;
+ private String[] mBroadcastGenres;
+ private String[] mCanonicalGenres;
+ private String mShortDescription;
+ private String mLongDescription;
+ private int mVideoWidth;
+ private int mVideoHeight;
+ private String mAudioLanguage;
+ private String mContentRating;
+ private String mPosterArtUri;
+ private String mThumbnailUri;
+ private boolean mSearchable = true;
+ private Uri mDataUri;
+ private long mDataBytes;
+ private long mDurationMillis;
+ private long mExpireTimeUtcMillis;
+ private int mInternalProviderFlag1;
+ private int mInternalProviderFlag2;
+ private int mInternalProviderFlag3;
+ private int mInternalProviderFlag4;
+ private int mVersionNumber;
+
+ public Builder setId(long id) {
+ mId = id;
+ return this;
+ }
+
+ public Builder setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ public Builder setInputId(String inputId) {
+ mInputId = inputId;
+ return this;
+ }
+
+ public Builder setChannelId(long channelId) {
+ mChannelId = channelId;
+ return this;
+ }
+
+ public Builder setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ public Builder setSeriesId(String seriesId) {
+ mSeriesId = seriesId;
+ return this;
+ }
+
+ public Builder setSeasonNumber(String seasonNumber) {
+ mSeasonNumber = seasonNumber;
+ return this;
+ }
+
+ public Builder setSeasonTitle(String seasonTitle) {
+ mSeasonTitle = seasonTitle;
+ return this;
+ }
+
+ public Builder setEpisodeNumber(String episodeNumber) {
+ mEpisodeNumber = episodeNumber;
+ return this;
+ }
+
+ public Builder setEpisodeTitle(String episodeTitle) {
+ mEpisodeTitle = episodeTitle;
+ return this;
+ }
+
+ public Builder setStartTimeUtcMillis(long startTimeUtcMillis) {
+ mStartTimeUtcMillis = startTimeUtcMillis;
+ return this;
+ }
+
+ public Builder setEndTimeUtcMillis(long endTimeUtcMillis) {
+ mEndTimeUtcMillis = endTimeUtcMillis;
+ return this;
+ }
+
+ public Builder setBroadcastGenres(String broadcastGenres) {
+ if (TextUtils.isEmpty(broadcastGenres)) {
+ mBroadcastGenres = null;
+ return this;
+ }
+ return setBroadcastGenres(TvContract.Programs.Genres.decode(broadcastGenres));
+ }
+
+ private Builder setBroadcastGenres(String[] broadcastGenres) {
+ mBroadcastGenres = broadcastGenres;
+ return this;
+ }
+
+ public Builder setCanonicalGenres(String canonicalGenres) {
+ if (TextUtils.isEmpty(canonicalGenres)) {
+ mCanonicalGenres = null;
+ return this;
+ }
+ return setCanonicalGenres(TvContract.Programs.Genres.decode(canonicalGenres));
+ }
+
+ private Builder setCanonicalGenres(String[] canonicalGenres) {
+ mCanonicalGenres = canonicalGenres;
+ return this;
+ }
+
+ public Builder setShortDescription(String shortDescription) {
+ mShortDescription = shortDescription;
+ return this;
+ }
+
+ public Builder setLongDescription(String longDescription) {
+ mLongDescription = longDescription;
+ return this;
+ }
+
+ public Builder setVideoWidth(int videoWidth) {
+ mVideoWidth = videoWidth;
+ return this;
+ }
+
+ public Builder setVideoHeight(int videoHeight) {
+ mVideoHeight = videoHeight;
+ return this;
+ }
+
+ public Builder setAudioLanguage(String audioLanguage) {
+ mAudioLanguage = audioLanguage;
+ return this;
+ }
+
+ public Builder setContentRating(String contentRating) {
+ mContentRating = contentRating;
+ return this;
+ }
+
+ private Uri toUri(String uriString) {
+ try {
+ return uriString == null ? null : Uri.parse(uriString);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public Builder setPosterArtUri(String posterArtUri) {
+ mPosterArtUri = posterArtUri;
+ return this;
+ }
+
+ public Builder setThumbnailUri(String thumbnailUri) {
+ mThumbnailUri = thumbnailUri;
+ return this;
+ }
+
+ public Builder setSearchable(boolean searchable) {
+ mSearchable = searchable;
+ return this;
+ }
+
+ public Builder setDataUri(String dataUri) {
+ return setDataUri(toUri(dataUri));
+ }
+
+ public Builder setDataUri(Uri dataUri) {
+ mDataUri = dataUri;
+ return this;
+ }
+
+ public Builder setDataBytes(long dataBytes) {
+ mDataBytes = dataBytes;
+ return this;
+ }
+
+ public Builder setDurationMillis(long durationMillis) {
+ mDurationMillis = durationMillis;
+ return this;
+ }
+
+ public Builder setExpireTimeUtcMillis(long expireTimeUtcMillis) {
+ mExpireTimeUtcMillis = expireTimeUtcMillis;
+ return this;
+ }
+
+ public Builder setInternalProviderFlag1(int internalProviderFlag1) {
+ mInternalProviderFlag1 = internalProviderFlag1;
+ return this;
+ }
+
+ public Builder setInternalProviderFlag2(int internalProviderFlag2) {
+ mInternalProviderFlag2 = internalProviderFlag2;
+ return this;
+ }
+
+ public Builder setInternalProviderFlag3(int internalProviderFlag3) {
+ mInternalProviderFlag3 = internalProviderFlag3;
+ return this;
+ }
+
+ public Builder setInternalProviderFlag4(int internalProviderFlag4) {
+ mInternalProviderFlag4 = internalProviderFlag4;
+ return this;
+ }
+
+ public Builder setVersionNumber(int versionNumber) {
+ mVersionNumber = versionNumber;
+ return this;
+ }
+
+ public RecordedProgram build() {
+ // Generate the series ID for the episodic program of other TV input.
+ if (TextUtils.isEmpty(mSeriesId)
+ && !TextUtils.isEmpty(mEpisodeNumber)) {
+ setSeriesId(BaseProgram.generateSeriesId(mPackageName, mTitle));
+ }
+ return new RecordedProgram(mId, mPackageName, mInputId, mChannelId, mTitle, mSeriesId,
+ mSeasonNumber, mSeasonTitle, mEpisodeNumber, mEpisodeTitle, mStartTimeUtcMillis,
+ mEndTimeUtcMillis, mBroadcastGenres, mCanonicalGenres, mShortDescription,
+ mLongDescription, mVideoWidth, mVideoHeight, mAudioLanguage, mContentRating,
+ mPosterArtUri, mThumbnailUri, mSearchable, mDataUri, mDataBytes,
+ mDurationMillis, mExpireTimeUtcMillis, mInternalProviderFlag1,
+ mInternalProviderFlag2, mInternalProviderFlag3, mInternalProviderFlag4,
+ mVersionNumber);
+ }
+ }
+
+ public static Builder builder() { return new Builder(); }
+
+ public static Builder buildFrom(RecordedProgram orig) {
+ return builder()
+ .setId(orig.getId())
+ .setPackageName(orig.getPackageName())
+ .setInputId(orig.getInputId())
+ .setChannelId(orig.getChannelId())
+ .setTitle(orig.getTitle())
+ .setSeriesId(orig.getSeriesId())
+ .setSeasonNumber(orig.getSeasonNumber())
+ .setSeasonTitle(orig.getSeasonTitle())
+ .setEpisodeNumber(orig.getEpisodeNumber())
+ .setEpisodeTitle(orig.getEpisodeTitle())
+ .setStartTimeUtcMillis(orig.getStartTimeUtcMillis())
+ .setEndTimeUtcMillis(orig.getEndTimeUtcMillis())
+ .setBroadcastGenres(orig.getBroadcastGenres())
+ .setCanonicalGenres(orig.getCanonicalGenres())
+ .setShortDescription(orig.getDescription())
+ .setLongDescription(orig.getLongDescription())
+ .setVideoWidth(orig.getVideoWidth())
+ .setVideoHeight(orig.getVideoHeight())
+ .setAudioLanguage(orig.getAudioLanguage())
+ .setContentRating(orig.getContentRating())
+ .setPosterArtUri(orig.getPosterArtUri())
+ .setThumbnailUri(orig.getThumbnailUri())
+ .setSearchable(orig.isSearchable())
+ .setInternalProviderFlag1(orig.getInternalProviderFlag1())
+ .setInternalProviderFlag2(orig.getInternalProviderFlag2())
+ .setInternalProviderFlag3(orig.getInternalProviderFlag3())
+ .setInternalProviderFlag4(orig.getInternalProviderFlag4())
+ .setVersionNumber(orig.getVersionNumber());
+ }
+
+ public static final Comparator<RecordedProgram> START_TIME_THEN_ID_COMPARATOR =
+ new Comparator<RecordedProgram>() {
+ @Override
+ public int compare(RecordedProgram lhs, RecordedProgram rhs) {
+ int res =
+ Long.compare(lhs.getStartTimeUtcMillis(), rhs.getStartTimeUtcMillis());
+ if (res != 0) {
+ return res;
+ }
+ return Long.compare(lhs.mId, rhs.mId);
+ }
+ };
+
+ private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5);
+
+ private final long mId;
+ private final String mPackageName;
+ private final String mInputId;
+ private final long mChannelId;
+ private final String mTitle;
+ private final String mSeriesId;
+ private final String mSeasonNumber;
+ private final String mSeasonTitle;
+ private final String mEpisodeNumber;
+ private final String mEpisodeTitle;
+ private final long mStartTimeUtcMillis;
+ private final long mEndTimeUtcMillis;
+ private final String[] mBroadcastGenres;
+ private final String[] mCanonicalGenres;
+ private final String mShortDescription;
+ private final String mLongDescription;
+ private final int mVideoWidth;
+ private final int mVideoHeight;
+ private final String mAudioLanguage;
+ private final String mContentRating;
+ private final String mPosterArtUri;
+ private final String mThumbnailUri;
+ private final boolean mSearchable;
+ private final Uri mDataUri;
+ private final long mDataBytes;
+ private final long mDurationMillis;
+ private final long mExpireTimeUtcMillis;
+ private final int mInternalProviderFlag1;
+ private final int mInternalProviderFlag2;
+ private final int mInternalProviderFlag3;
+ private final int mInternalProviderFlag4;
+ private final int mVersionNumber;
+
+ private RecordedProgram(long id, String packageName, String inputId, long channelId,
+ String title, String seriesId, String seasonNumber, String seasonTitle,
+ String episodeNumber, String episodeTitle, long startTimeUtcMillis,
+ long endTimeUtcMillis, String[] broadcastGenres, String[] canonicalGenres,
+ String shortDescription, String longDescription, int videoWidth, int videoHeight,
+ String audioLanguage, String contentRating, String posterArtUri, String thumbnailUri,
+ boolean searchable, Uri dataUri, long dataBytes, long durationMillis,
+ long expireTimeUtcMillis, int internalProviderFlag1, int internalProviderFlag2,
+ int internalProviderFlag3, int internalProviderFlag4, int versionNumber) {
+ mId = id;
+ mPackageName = packageName;
+ mInputId = inputId;
+ mChannelId = channelId;
+ mTitle = title;
+ mSeriesId = seriesId;
+ mSeasonNumber = seasonNumber;
+ mSeasonTitle = seasonTitle;
+ mEpisodeNumber = episodeNumber;
+ mEpisodeTitle = episodeTitle;
+ mStartTimeUtcMillis = startTimeUtcMillis;
+ mEndTimeUtcMillis = endTimeUtcMillis;
+ mBroadcastGenres = broadcastGenres;
+ mCanonicalGenres = canonicalGenres;
+ mShortDescription = shortDescription;
+ mLongDescription = longDescription;
+ mVideoWidth = videoWidth;
+ mVideoHeight = videoHeight;
+
+ mAudioLanguage = audioLanguage;
+ mContentRating = contentRating;
+ mPosterArtUri = posterArtUri;
+ mThumbnailUri = thumbnailUri;
+ mSearchable = searchable;
+ mDataUri = dataUri;
+ mDataBytes = dataBytes;
+ mDurationMillis = durationMillis;
+ mExpireTimeUtcMillis = expireTimeUtcMillis;
+ mInternalProviderFlag1 = internalProviderFlag1;
+ mInternalProviderFlag2 = internalProviderFlag2;
+ mInternalProviderFlag3 = internalProviderFlag3;
+ mInternalProviderFlag4 = internalProviderFlag4;
+ mVersionNumber = versionNumber;
+ }
+
+ public String getAudioLanguage() {
+ return mAudioLanguage;
+ }
+
+ public String[] getBroadcastGenres() {
+ return mBroadcastGenres;
+ }
+
+ public String[] getCanonicalGenres() {
+ return mCanonicalGenres;
+ }
+
+ /**
+ * Returns array of canonical genre ID's for this recorded program.
+ */
+ @Override
+ public int[] getCanonicalGenreIds() {
+ if (mCanonicalGenres == null) {
+ return null;
+ }
+ int[] genreIds = new int[mCanonicalGenres.length];
+ for (int i = 0; i < mCanonicalGenres.length; i++) {
+ genreIds[i] = GenreItems.getId(mCanonicalGenres[i]);
+ }
+ return genreIds;
+ }
+
+ @Override
+ public long getChannelId() {
+ return mChannelId;
+ }
+
+ public String getContentRating() {
+ return mContentRating;
+ }
+
+ public Uri getDataUri() {
+ return mDataUri;
+ }
+
+ public long getDataBytes() {
+ return mDataBytes;
+ }
+
+ @Override
+ public long getDurationMillis() {
+ return mDurationMillis;
+ }
+
+ @Override
+ public long getEndTimeUtcMillis() {
+ return mEndTimeUtcMillis;
+ }
+
+ @Override
+ public String getEpisodeNumber() {
+ return mEpisodeNumber;
+ }
+
+ public String getEpisodeTitle() {
+ return mEpisodeTitle;
+ }
+
+ @Override
+ public String getEpisodeDisplayTitle(Context context) {
+ if (!TextUtils.isEmpty(mEpisodeNumber)) {
+ String episodeTitle = mEpisodeTitle == null ? "" : mEpisodeTitle;
+ if (TextUtils.equals(mSeasonNumber, "0")) {
+ // Do not show "S0: ".
+ return String.format(context.getResources().getString(
+ R.string.display_episode_title_format_no_season_number),
+ mEpisodeNumber, episodeTitle);
+ } else {
+ return String.format(context.getResources().getString(
+ R.string.display_episode_title_format),
+ mSeasonNumber, mEpisodeNumber, episodeTitle);
+ }
+ }
+ return mEpisodeTitle;
+ }
+
+ @Nullable
+ @Override
+ public String getTitleWithEpisodeNumber(Context context) {
+ if (TextUtils.isEmpty(mTitle)) {
+ return mTitle;
+ }
+ if (TextUtils.isEmpty(mSeasonNumber) || mSeasonNumber.equals("0")) {
+ return TextUtils.isEmpty(mEpisodeNumber) ? mTitle : context.getString(
+ R.string.program_title_with_episode_number_no_season, mTitle, mEpisodeNumber);
+ } else {
+ return context.getString(R.string.program_title_with_episode_number, mTitle,
+ mSeasonNumber, mEpisodeNumber);
+ }
+ }
+
+ @Nullable
+ public String getEpisodeDisplayNumber(Context context) {
+ if (!TextUtils.isEmpty(mEpisodeNumber)) {
+ if (TextUtils.equals(mSeasonNumber, "0")) {
+ // Do not show "S0: ".
+ return String.format(context.getResources().getString(
+ R.string.display_episode_number_format_no_season_number), mEpisodeNumber);
+ } else {
+ return String.format(context.getResources().getString(
+ R.string.display_episode_number_format), mSeasonNumber, mEpisodeNumber);
+ }
+ }
+ return null;
+ }
+
+ public long getExpireTimeUtcMillis() {
+ return mExpireTimeUtcMillis;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public String getInputId() {
+ return mInputId;
+ }
+
+ public int getInternalProviderFlag1() {
+ return mInternalProviderFlag1;
+ }
+
+ public int getInternalProviderFlag2() {
+ return mInternalProviderFlag2;
+ }
+
+ public int getInternalProviderFlag3() {
+ return mInternalProviderFlag3;
+ }
+
+ public int getInternalProviderFlag4() {
+ return mInternalProviderFlag4;
+ }
+
+ @Override
+ public String getDescription() {
+ return mShortDescription;
+ }
+
+ @Override
+ public String getLongDescription() {
+ return mLongDescription;
+ }
+
+ @Override
+ public String getPosterArtUri() {
+ return mPosterArtUri;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ public boolean isSearchable() {
+ return mSearchable;
+ }
+
+ @Override
+ public String getSeriesId() {
+ return mSeriesId;
+ }
+
+ @Override
+ public String getSeasonNumber() {
+ return mSeasonNumber;
+ }
+
+ public String getSeasonTitle() {
+ return mSeasonTitle;
+ }
+
+ @Override
+ public long getStartTimeUtcMillis() {
+ return mStartTimeUtcMillis;
+ }
+
+ @Override
+ public String getThumbnailUri() {
+ return mThumbnailUri;
+ }
+
+ @Override
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public Uri getUri() {
+ return ContentUris.withAppendedId(RecordedPrograms.CONTENT_URI, mId);
+ }
+
+ public int getVersionNumber() {
+ return mVersionNumber;
+ }
+
+ public int getVideoHeight() {
+ return mVideoHeight;
+ }
+
+ public int getVideoWidth() {
+ return mVideoWidth;
+ }
+
+ /**
+ * Checks whether the recording has been clipped or not.
+ */
+ public boolean isClipped() {
+ return mEndTimeUtcMillis - mStartTimeUtcMillis - mDurationMillis > CLIPPED_THRESHOLD_MS;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RecordedProgram that = (RecordedProgram) o;
+ return Objects.equals(mId, that.mId) &&
+ Objects.equals(mChannelId, that.mChannelId) &&
+ Objects.equals(mSeriesId, that.mSeriesId) &&
+ Objects.equals(mSeasonNumber, that.mSeasonNumber) &&
+ Objects.equals(mSeasonTitle, that.mSeasonTitle) &&
+ Objects.equals(mEpisodeNumber, that.mEpisodeNumber) &&
+ Objects.equals(mStartTimeUtcMillis, that.mStartTimeUtcMillis) &&
+ Objects.equals(mEndTimeUtcMillis, that.mEndTimeUtcMillis) &&
+ Objects.equals(mVideoWidth, that.mVideoWidth) &&
+ Objects.equals(mVideoHeight, that.mVideoHeight) &&
+ Objects.equals(mSearchable, that.mSearchable) &&
+ Objects.equals(mDataBytes, that.mDataBytes) &&
+ Objects.equals(mDurationMillis, that.mDurationMillis) &&
+ Objects.equals(mExpireTimeUtcMillis, that.mExpireTimeUtcMillis) &&
+ Objects.equals(mInternalProviderFlag1, that.mInternalProviderFlag1) &&
+ Objects.equals(mInternalProviderFlag2, that.mInternalProviderFlag2) &&
+ Objects.equals(mInternalProviderFlag3, that.mInternalProviderFlag3) &&
+ Objects.equals(mInternalProviderFlag4, that.mInternalProviderFlag4) &&
+ Objects.equals(mVersionNumber, that.mVersionNumber) &&
+ Objects.equals(mTitle, that.mTitle) &&
+ Objects.equals(mEpisodeTitle, that.mEpisodeTitle) &&
+ Arrays.equals(mBroadcastGenres, that.mBroadcastGenres) &&
+ Arrays.equals(mCanonicalGenres, that.mCanonicalGenres) &&
+ Objects.equals(mShortDescription, that.mShortDescription) &&
+ Objects.equals(mLongDescription, that.mLongDescription) &&
+ Objects.equals(mAudioLanguage, that.mAudioLanguage) &&
+ Objects.equals(mContentRating, that.mContentRating) &&
+ Objects.equals(mPosterArtUri, that.mPosterArtUri) &&
+ Objects.equals(mThumbnailUri, that.mThumbnailUri);
+ }
+
+ /**
+ * Hashes based on the ID.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId);
+ }
+
+ @Override
+ public String toString() {
+ return "RecordedProgram"
+ + "[" + mId +
+ "]{ mPackageName=" + mPackageName +
+ ", mInputId='" + mInputId + '\'' +
+ ", mChannelId='" + mChannelId + '\'' +
+ ", mTitle='" + mTitle + '\'' +
+ ", mSeriesId='" + mSeriesId + '\'' +
+ ", mEpisodeNumber=" + mEpisodeNumber +
+ ", mEpisodeTitle='" + mEpisodeTitle + '\'' +
+ ", mStartTimeUtcMillis=" + mStartTimeUtcMillis +
+ ", mEndTimeUtcMillis=" + mEndTimeUtcMillis +
+ ", mBroadcastGenres=" +
+ (mBroadcastGenres != null ? Arrays.toString(mBroadcastGenres) : "null") +
+ ", mCanonicalGenres=" +
+ (mCanonicalGenres != null ? Arrays.toString(mCanonicalGenres) : "null") +
+ ", mShortDescription='" + mShortDescription + '\'' +
+ ", mLongDescription='" + mLongDescription + '\'' +
+ ", mVideoHeight=" + mVideoHeight +
+ ", mVideoWidth=" + mVideoWidth +
+ ", mAudioLanguage='" + mAudioLanguage + '\'' +
+ ", mContentRating='" + mContentRating + '\'' +
+ ", mPosterArtUri=" + mPosterArtUri +
+ ", mThumbnailUri=" + mThumbnailUri +
+ ", mSearchable=" + mSearchable +
+ ", mDataUri=" + mDataUri +
+ ", mDataBytes=" + mDataBytes +
+ ", mDurationMillis=" + mDurationMillis +
+ ", mExpireTimeUtcMillis=" + mExpireTimeUtcMillis +
+ ", mInternalProviderFlag1=" + mInternalProviderFlag1 +
+ ", mInternalProviderFlag2=" + mInternalProviderFlag2 +
+ ", mInternalProviderFlag3=" + mInternalProviderFlag3 +
+ ", mInternalProviderFlag4=" + mInternalProviderFlag4 +
+ ", mSeasonNumber=" + mSeasonNumber +
+ ", mSeasonTitle=" + mSeasonTitle +
+ ", mVersionNumber=" + mVersionNumber +
+ '}';
+ }
+
+ @Nullable
+ private static String safeToString(@Nullable Object o) {
+ return o == null ? null : o.toString();
+ }
+
+ @Nullable
+ private static String safeEncode(@Nullable String[] genres) {
+ return genres == null ? null : TvContract.Programs.Genres.encode(genres);
+ }
+
+ /**
+ * Returns an array containing all of the elements in the list.
+ */
+ public static RecordedProgram[] toArray(Collection<RecordedProgram> recordedPrograms) {
+ return recordedPrograms.toArray(new RecordedProgram[recordedPrograms.size()]);
+ }
+}
diff --git a/src/com/android/tv/dvr/data/ScheduledRecording.java b/src/com/android/tv/dvr/data/ScheduledRecording.java
new file mode 100644
index 00000000..88849a2c
--- /dev/null
+++ b/src/com/android/tv/dvr/data/ScheduledRecording.java
@@ -0,0 +1,902 @@
+/*
+ * 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.dvr.data;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.IntDef;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Range;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.data.Channel;
+import com.android.tv.data.Program;
+import com.android.tv.dvr.DvrScheduleManager;
+import com.android.tv.dvr.provider.DvrContract.Schedules;
+import com.android.tv.util.CompositeComparator;
+import com.android.tv.util.Utils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * A data class for one recording contents.
+ */
+@VisibleForTesting
+public final class ScheduledRecording implements Parcelable {
+ private static final String TAG = "ScheduledRecording";
+
+ /**
+ * Indicates that the ID is not assigned yet.
+ */
+ public static final long ID_NOT_SET = 0;
+
+ /**
+ * The default priority of the recording.
+ */
+ public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1;
+
+ /**
+ * Compares the start time in ascending order.
+ */
+ public static final Comparator<ScheduledRecording> START_TIME_COMPARATOR
+ = new Comparator<ScheduledRecording>() {
+ @Override
+ public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
+ return Long.compare(lhs.mStartTimeMs, rhs.mStartTimeMs);
+ }
+ };
+
+ /**
+ * Compares the end time in ascending order.
+ */
+ public static final Comparator<ScheduledRecording> END_TIME_COMPARATOR
+ = new Comparator<ScheduledRecording>() {
+ @Override
+ public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
+ return Long.compare(lhs.mEndTimeMs, rhs.mEndTimeMs);
+ }
+ };
+
+ /**
+ * Compares ID in ascending order. The schedule with the larger ID was created later.
+ */
+ public static final Comparator<ScheduledRecording> ID_COMPARATOR
+ = new Comparator<ScheduledRecording>() {
+ @Override
+ public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
+ return Long.compare(lhs.mId, rhs.mId);
+ }
+ };
+
+ /**
+ * Compares the priority in ascending order.
+ */
+ public static final Comparator<ScheduledRecording> PRIORITY_COMPARATOR
+ = new Comparator<ScheduledRecording>() {
+ @Override
+ public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
+ return Long.compare(lhs.mPriority, rhs.mPriority);
+ }
+ };
+
+ /**
+ * Compares start time in ascending order and then priority in descending order and then ID in
+ * descending order.
+ */
+ public static final Comparator<ScheduledRecording> START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR
+ = new CompositeComparator<>(START_TIME_COMPARATOR, PRIORITY_COMPARATOR.reversed(),
+ ID_COMPARATOR.reversed());
+
+ /**
+ * Builds scheduled recordings from programs.
+ */
+ public static Builder builder(String inputId, Program p) {
+ return new Builder()
+ .setInputId(inputId)
+ .setChannelId(p.getChannelId())
+ .setStartTimeMs(p.getStartTimeUtcMillis()).setEndTimeMs(p.getEndTimeUtcMillis())
+ .setProgramId(p.getId())
+ .setProgramTitle(p.getTitle())
+ .setSeasonNumber(p.getSeasonNumber())
+ .setEpisodeNumber(p.getEpisodeNumber())
+ .setEpisodeTitle(p.getEpisodeTitle())
+ .setProgramDescription(p.getDescription())
+ .setProgramLongDescription(p.getLongDescription())
+ .setProgramPosterArtUri(p.getPosterArtUri())
+ .setProgramThumbnailUri(p.getThumbnailUri())
+ .setType(TYPE_PROGRAM);
+ }
+
+ public static Builder builder(String inputId, long channelId, long startTime, long endTime) {
+ return new Builder()
+ .setInputId(inputId)
+ .setChannelId(channelId)
+ .setStartTimeMs(startTime)
+ .setEndTimeMs(endTime)
+ .setType(TYPE_TIMED);
+ }
+
+ /**
+ * Creates a new Builder with the values set from the {@link RecordedProgram}.
+ */
+ @VisibleForTesting
+ public static Builder builder(RecordedProgram p) {
+ boolean isProgramRecording = !TextUtils.isEmpty(p.getTitle());
+ return new Builder()
+ .setInputId(p.getInputId())
+ .setChannelId(p.getChannelId())
+ .setType(isProgramRecording ? TYPE_PROGRAM : TYPE_TIMED)
+ .setStartTimeMs(p.getStartTimeUtcMillis())
+ .setEndTimeMs(p.getEndTimeUtcMillis())
+ .setProgramTitle(p.getTitle())
+ .setSeasonNumber(p.getSeasonNumber())
+ .setEpisodeNumber(p.getEpisodeNumber())
+ .setEpisodeTitle(p.getEpisodeTitle())
+ .setProgramDescription(p.getDescription())
+ .setProgramLongDescription(p.getLongDescription())
+ .setProgramPosterArtUri(p.getPosterArtUri())
+ .setProgramThumbnailUri(p.getThumbnailUri())
+ .setState(STATE_RECORDING_FINISHED);
+ }
+
+ public static final class Builder {
+ private long mId = ID_NOT_SET;
+ private long mPriority = DvrScheduleManager.DEFAULT_PRIORITY;
+ private String mInputId;
+ private long mChannelId;
+ private long mProgramId = ID_NOT_SET;
+ private String mProgramTitle;
+ private @RecordingType int mType;
+ private long mStartTimeMs;
+ private long mEndTimeMs;
+ private String mSeasonNumber;
+ private String mEpisodeNumber;
+ private String mEpisodeTitle;
+ private String mProgramDescription;
+ private String mProgramLongDescription;
+ private String mProgramPosterArtUri;
+ private String mProgramThumbnailUri;
+ private @RecordingState int mState;
+ private long mSeriesRecordingId = ID_NOT_SET;
+
+ private Builder() { }
+
+ public Builder setId(long id) {
+ mId = id;
+ return this;
+ }
+
+ public Builder setPriority(long priority) {
+ mPriority = priority;
+ return this;
+ }
+
+ public Builder setInputId(String inputId) {
+ mInputId = inputId;
+ return this;
+ }
+
+ public Builder setChannelId(long channelId) {
+ mChannelId = channelId;
+ return this;
+ }
+
+ public Builder setProgramId(long programId) {
+ mProgramId = programId;
+ return this;
+ }
+
+ public Builder setProgramTitle(String programTitle) {
+ mProgramTitle = programTitle;
+ return this;
+ }
+
+ private Builder setType(@RecordingType int type) {
+ mType = type;
+ return this;
+ }
+
+ public Builder setStartTimeMs(long startTimeMs) {
+ mStartTimeMs = startTimeMs;
+ return this;
+ }
+
+ public Builder setEndTimeMs(long endTimeMs) {
+ mEndTimeMs = endTimeMs;
+ return this;
+ }
+
+ public Builder setSeasonNumber(String seasonNumber) {
+ mSeasonNumber = seasonNumber;
+ return this;
+ }
+
+ public Builder setEpisodeNumber(String episodeNumber) {
+ mEpisodeNumber = episodeNumber;
+ return this;
+ }
+
+ public Builder setEpisodeTitle(String episodeTitle) {
+ mEpisodeTitle = episodeTitle;
+ return this;
+ }
+
+ public Builder setProgramDescription(String description) {
+ mProgramDescription = description;
+ return this;
+ }
+
+ public Builder setProgramLongDescription(String longDescription) {
+ mProgramLongDescription = longDescription;
+ return this;
+ }
+
+ public Builder setProgramPosterArtUri(String programPosterArtUri) {
+ mProgramPosterArtUri = programPosterArtUri;
+ return this;
+ }
+
+ public Builder setProgramThumbnailUri(String programThumbnailUri) {
+ mProgramThumbnailUri = programThumbnailUri;
+ return this;
+ }
+
+ public Builder setState(@RecordingState int state) {
+ mState = state;
+ return this;
+ }
+
+ public Builder setSeriesRecordingId(long seriesRecordingId) {
+ mSeriesRecordingId = seriesRecordingId;
+ return this;
+ }
+
+ public ScheduledRecording build() {
+ return new ScheduledRecording(mId, mPriority, mInputId, mChannelId, mProgramId,
+ mProgramTitle, mType, mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber,
+ mEpisodeTitle, mProgramDescription, mProgramLongDescription,
+ mProgramPosterArtUri, mProgramThumbnailUri, mState, mSeriesRecordingId);
+ }
+ }
+
+ /**
+ * Creates {@link Builder} object from the given original {@code Recording}.
+ */
+ public static Builder buildFrom(ScheduledRecording orig) {
+ return new Builder()
+ .setId(orig.mId)
+ .setInputId(orig.mInputId)
+ .setChannelId(orig.mChannelId)
+ .setEndTimeMs(orig.mEndTimeMs)
+ .setSeriesRecordingId(orig.mSeriesRecordingId)
+ .setPriority(orig.mPriority)
+ .setProgramId(orig.mProgramId)
+ .setProgramTitle(orig.mProgramTitle)
+ .setStartTimeMs(orig.mStartTimeMs)
+ .setSeasonNumber(orig.getSeasonNumber())
+ .setEpisodeNumber(orig.getEpisodeNumber())
+ .setEpisodeTitle(orig.getEpisodeTitle())
+ .setProgramDescription(orig.getProgramDescription())
+ .setProgramLongDescription(orig.getProgramLongDescription())
+ .setProgramPosterArtUri(orig.getProgramPosterArtUri())
+ .setProgramThumbnailUri(orig.getProgramThumbnailUri())
+ .setState(orig.mState).setType(orig.mType);
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATE_RECORDING_NOT_STARTED, STATE_RECORDING_IN_PROGRESS, STATE_RECORDING_FINISHED,
+ STATE_RECORDING_FAILED, STATE_RECORDING_CLIPPED, STATE_RECORDING_DELETED,
+ STATE_RECORDING_CANCELED})
+ public @interface RecordingState {}
+ public static final int STATE_RECORDING_NOT_STARTED = 0;
+ public static final int STATE_RECORDING_IN_PROGRESS = 1;
+ public static final int STATE_RECORDING_FINISHED = 2;
+ public static final int STATE_RECORDING_FAILED = 3;
+ public static final int STATE_RECORDING_CLIPPED = 4;
+ public static final int STATE_RECORDING_DELETED = 5;
+ public static final int STATE_RECORDING_CANCELED = 6;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TYPE_TIMED, TYPE_PROGRAM})
+ public @interface RecordingType {}
+ /**
+ * Record with given time range.
+ */
+ public static final int TYPE_TIMED = 1;
+ /**
+ * Record with a given program.
+ */
+ public static final int TYPE_PROGRAM = 2;
+
+ @RecordingType private final int mType;
+
+ /**
+ * Use this projection if you want to create {@link ScheduledRecording} object using
+ * {@link #fromCursor}.
+ */
+ public static final String[] PROJECTION = {
+ // Columns must match what is read in #fromCursor
+ Schedules._ID,
+ Schedules.COLUMN_PRIORITY,
+ Schedules.COLUMN_TYPE,
+ Schedules.COLUMN_INPUT_ID,
+ Schedules.COLUMN_CHANNEL_ID,
+ Schedules.COLUMN_PROGRAM_ID,
+ Schedules.COLUMN_PROGRAM_TITLE,
+ Schedules.COLUMN_START_TIME_UTC_MILLIS,
+ Schedules.COLUMN_END_TIME_UTC_MILLIS,
+ Schedules.COLUMN_SEASON_NUMBER,
+ Schedules.COLUMN_EPISODE_NUMBER,
+ Schedules.COLUMN_EPISODE_TITLE,
+ Schedules.COLUMN_PROGRAM_DESCRIPTION,
+ Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION,
+ Schedules.COLUMN_PROGRAM_POST_ART_URI,
+ Schedules.COLUMN_PROGRAM_THUMBNAIL_URI,
+ Schedules.COLUMN_STATE,
+ Schedules.COLUMN_SERIES_RECORDING_ID};
+
+ /**
+ * Creates {@link ScheduledRecording} object from the given {@link Cursor}.
+ */
+ public static ScheduledRecording fromCursor(Cursor c) {
+ int index = -1;
+ return new Builder()
+ .setId(c.getLong(++index))
+ .setPriority(c.getLong(++index))
+ .setType(recordingType(c.getString(++index)))
+ .setInputId(c.getString(++index))
+ .setChannelId(c.getLong(++index))
+ .setProgramId(c.getLong(++index))
+ .setProgramTitle(c.getString(++index))
+ .setStartTimeMs(c.getLong(++index))
+ .setEndTimeMs(c.getLong(++index))
+ .setSeasonNumber(c.getString(++index))
+ .setEpisodeNumber(c.getString(++index))
+ .setEpisodeTitle(c.getString(++index))
+ .setProgramDescription(c.getString(++index))
+ .setProgramLongDescription(c.getString(++index))
+ .setProgramPosterArtUri(c.getString(++index))
+ .setProgramThumbnailUri(c.getString(++index))
+ .setState(recordingState(c.getString(++index)))
+ .setSeriesRecordingId(c.getLong(++index))
+ .build();
+ }
+
+ public static ContentValues toContentValues(ScheduledRecording r) {
+ ContentValues values = new ContentValues();
+ if (r.getId() != ID_NOT_SET) {
+ values.put(Schedules._ID, r.getId());
+ }
+ values.put(Schedules.COLUMN_INPUT_ID, r.getInputId());
+ values.put(Schedules.COLUMN_CHANNEL_ID, r.getChannelId());
+ values.put(Schedules.COLUMN_PROGRAM_ID, r.getProgramId());
+ values.put(Schedules.COLUMN_PROGRAM_TITLE, r.getProgramTitle());
+ values.put(Schedules.COLUMN_PRIORITY, r.getPriority());
+ values.put(Schedules.COLUMN_START_TIME_UTC_MILLIS, r.getStartTimeMs());
+ values.put(Schedules.COLUMN_END_TIME_UTC_MILLIS, r.getEndTimeMs());
+ values.put(Schedules.COLUMN_SEASON_NUMBER, r.getSeasonNumber());
+ values.put(Schedules.COLUMN_EPISODE_NUMBER, r.getEpisodeNumber());
+ values.put(Schedules.COLUMN_EPISODE_TITLE, r.getEpisodeTitle());
+ values.put(Schedules.COLUMN_PROGRAM_DESCRIPTION, r.getProgramDescription());
+ values.put(Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, r.getProgramLongDescription());
+ values.put(Schedules.COLUMN_PROGRAM_POST_ART_URI, r.getProgramPosterArtUri());
+ values.put(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, r.getProgramThumbnailUri());
+ values.put(Schedules.COLUMN_STATE, recordingState(r.getState()));
+ values.put(Schedules.COLUMN_TYPE, recordingType(r.getType()));
+ if (r.getSeriesRecordingId() != ID_NOT_SET) {
+ values.put(Schedules.COLUMN_SERIES_RECORDING_ID, r.getSeriesRecordingId());
+ } else {
+ values.putNull(Schedules.COLUMN_SERIES_RECORDING_ID);
+ }
+ return values;
+ }
+
+ public static ScheduledRecording fromParcel(Parcel in) {
+ return new Builder()
+ .setId(in.readLong())
+ .setPriority(in.readLong())
+ .setInputId(in.readString())
+ .setChannelId(in.readLong())
+ .setProgramId(in.readLong())
+ .setProgramTitle(in.readString())
+ .setType(in.readInt())
+ .setStartTimeMs(in.readLong())
+ .setEndTimeMs(in.readLong())
+ .setSeasonNumber(in.readString())
+ .setEpisodeNumber(in.readString())
+ .setEpisodeTitle(in.readString())
+ .setProgramDescription(in.readString())
+ .setProgramLongDescription(in.readString())
+ .setProgramPosterArtUri(in.readString())
+ .setProgramThumbnailUri(in.readString())
+ .setState(in.readInt())
+ .setSeriesRecordingId(in.readLong())
+ .build();
+ }
+
+ public static final Parcelable.Creator<ScheduledRecording> CREATOR =
+ new Parcelable.Creator<ScheduledRecording>() {
+ @Override
+ public ScheduledRecording createFromParcel(Parcel in) {
+ return ScheduledRecording.fromParcel(in);
+ }
+
+ @Override
+ public ScheduledRecording[] newArray(int size) {
+ return new ScheduledRecording[size];
+ }
+ };
+
+ /**
+ * The ID internal to Live TV
+ */
+ private long mId;
+
+ /**
+ * The priority of this recording.
+ *
+ * <p> The highest number is recorded first. If there is a tie in priority then the higher id
+ * wins.
+ */
+ private final long mPriority;
+
+ private final String mInputId;
+ private final long mChannelId;
+ /**
+ * Optional id of the associated program.
+ */
+ private final long mProgramId;
+ private final String mProgramTitle;
+
+ private final long mStartTimeMs;
+ private final long mEndTimeMs;
+ private final String mSeasonNumber;
+ private final String mEpisodeNumber;
+ private final String mEpisodeTitle;
+ private final String mProgramDescription;
+ private final String mProgramLongDescription;
+ private final String mProgramPosterArtUri;
+ private final String mProgramThumbnailUri;
+ @RecordingState private final int mState;
+ private final long mSeriesRecordingId;
+
+ private ScheduledRecording(long id, long priority, String inputId, long channelId, long programId,
+ String programTitle, @RecordingType int type, long startTime, long endTime,
+ String seasonNumber, String episodeNumber, String episodeTitle,
+ String programDescription, String programLongDescription, String programPosterArtUri,
+ String programThumbnailUri, @RecordingState int state, long seriesRecordingId) {
+ mId = id;
+ mPriority = priority;
+ mInputId = inputId;
+ mChannelId = channelId;
+ mProgramId = programId;
+ mProgramTitle = programTitle;
+ mType = type;
+ mStartTimeMs = startTime;
+ mEndTimeMs = endTime;
+ mSeasonNumber = seasonNumber;
+ mEpisodeNumber = episodeNumber;
+ mEpisodeTitle = episodeTitle;
+ mProgramDescription = programDescription;
+ mProgramLongDescription = programLongDescription;
+ mProgramPosterArtUri = programPosterArtUri;
+ mProgramThumbnailUri = programThumbnailUri;
+ mState = state;
+ mSeriesRecordingId = seriesRecordingId;
+ }
+
+ /**
+ * Returns recording schedule type. The possible types are {@link #TYPE_PROGRAM} and
+ * {@link #TYPE_TIMED}.
+ */
+ @RecordingType
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns schedules' input id.
+ */
+ public String getInputId() {
+ return mInputId;
+ }
+
+ /**
+ * Returns recorded {@link Channel}.
+ */
+ public long getChannelId() {
+ return mChannelId;
+ }
+
+ /**
+ * Return the optional program id
+ */
+ public long getProgramId() {
+ return mProgramId;
+ }
+
+ /**
+ * Return the optional program Title
+ */
+ public String getProgramTitle() {
+ return mProgramTitle;
+ }
+
+ /**
+ * Returns started time.
+ */
+ public long getStartTimeMs() {
+ return mStartTimeMs;
+ }
+
+ /**
+ * Returns ended time.
+ */
+ public long getEndTimeMs() {
+ return mEndTimeMs;
+ }
+
+ /**
+ * Returns the season number.
+ */
+ public String getSeasonNumber() {
+ return mSeasonNumber;
+ }
+
+ /**
+ * Returns the episode number.
+ */
+ public String getEpisodeNumber() {
+ return mEpisodeNumber;
+ }
+
+ /**
+ * Returns the episode title.
+ */
+ public String getEpisodeTitle() {
+ return mEpisodeTitle;
+ }
+
+ /**
+ * Returns the description of program.
+ */
+ public String getProgramDescription() {
+ return mProgramDescription;
+ }
+
+ /**
+ * Returns the long description of program.
+ */
+ public String getProgramLongDescription() {
+ return mProgramLongDescription;
+ }
+
+ /**
+ * Returns the poster uri of program.
+ */
+ public String getProgramPosterArtUri() {
+ return mProgramPosterArtUri;
+ }
+
+ /**
+ * Returns the thumb nail uri of program.
+ */
+ public String getProgramThumbnailUri() {
+ return mProgramThumbnailUri;
+ }
+
+ /**
+ * Returns duration.
+ */
+ public long getDuration() {
+ return mEndTimeMs - mStartTimeMs;
+ }
+
+ /**
+ * Returns the state. The possible states are {@link #STATE_RECORDING_NOT_STARTED},
+ * {@link #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED},
+ * {@link #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and
+ * {@link #STATE_RECORDING_DELETED}.
+ */
+ @RecordingState public int getState() {
+ return mState;
+ }
+
+ /**
+ * Returns the ID of the {@link SeriesRecording} including this schedule.
+ */
+ public long getSeriesRecordingId() {
+ return mSeriesRecordingId;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Sets the ID;
+ */
+ public void setId(long id) {
+ mId = id;
+ }
+
+ public long getPriority() {
+ return mPriority;
+ }
+
+ /**
+ * Returns season number, episode number and episode title for display.
+ */
+ public String getEpisodeDisplayTitle(Context context) {
+ if (!TextUtils.isEmpty(mEpisodeNumber)) {
+ String episodeTitle = mEpisodeTitle == null ? "" : mEpisodeTitle;
+ if (TextUtils.equals(mSeasonNumber, "0")) {
+ // Do not show "S0: ".
+ return String.format(context.getResources().getString(
+ R.string.display_episode_title_format_no_season_number),
+ mEpisodeNumber, episodeTitle);
+ } else {
+ return String.format(context.getResources().getString(
+ R.string.display_episode_title_format),
+ mSeasonNumber, mEpisodeNumber, episodeTitle);
+ }
+ }
+ return mEpisodeTitle;
+ }
+
+ /**
+ * Returns the program's title withe its season and episode number.
+ */
+ public String getProgramTitleWithEpisodeNumber(Context context) {
+ if (TextUtils.isEmpty(mProgramTitle)) {
+ return mProgramTitle;
+ }
+ if (TextUtils.isEmpty(mSeasonNumber) || mSeasonNumber.equals("0")) {
+ return TextUtils.isEmpty(mEpisodeNumber) ? mProgramTitle : context.getString(
+ R.string.program_title_with_episode_number_no_season, mProgramTitle,
+ mEpisodeNumber);
+ } else {
+ return context.getString(R.string.program_title_with_episode_number, mProgramTitle,
+ mSeasonNumber, mEpisodeNumber);
+ }
+ }
+
+ /**
+ * Returns the program's display title, if the program title is not null, returns program title.
+ * Otherwise returns the channel name.
+ */
+ public String getProgramDisplayTitle(Context context) {
+ if (!TextUtils.isEmpty(mProgramTitle)) {
+ return mProgramTitle;
+ }
+ Channel channel = TvApplication.getSingletons(context).getChannelDataManager()
+ .getChannel(mChannelId);
+ return channel != null ? channel.getDisplayName()
+ : context.getString(R.string.no_program_information);
+ }
+
+ /**
+ * Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}.
+ */
+ private static @RecordingType int recordingType(String type) {
+ switch (type) {
+ case Schedules.TYPE_TIMED:
+ return TYPE_TIMED;
+ case Schedules.TYPE_PROGRAM:
+ return TYPE_PROGRAM;
+ default:
+ SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type);
+ return TYPE_TIMED;
+ }
+ }
+
+ /**
+ * Converts a @RecordingType int to a string, defaulting to {@link Schedules#TYPE_TIMED}.
+ */
+ private static String recordingType(@RecordingType int type) {
+ switch (type) {
+ case TYPE_TIMED:
+ return Schedules.TYPE_TIMED;
+ case TYPE_PROGRAM:
+ return Schedules.TYPE_PROGRAM;
+ default:
+ SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type);
+ return Schedules.TYPE_TIMED;
+ }
+ }
+
+ /**
+ * Converts a string to a @RecordingState int, defaulting to
+ * {@link #STATE_RECORDING_NOT_STARTED}.
+ */
+ private static @RecordingState int recordingState(String state) {
+ switch (state) {
+ case Schedules.STATE_RECORDING_NOT_STARTED:
+ return STATE_RECORDING_NOT_STARTED;
+ case Schedules.STATE_RECORDING_IN_PROGRESS:
+ return STATE_RECORDING_IN_PROGRESS;
+ case Schedules.STATE_RECORDING_FINISHED:
+ return STATE_RECORDING_FINISHED;
+ case Schedules.STATE_RECORDING_FAILED:
+ return STATE_RECORDING_FAILED;
+ case Schedules.STATE_RECORDING_CLIPPED:
+ return STATE_RECORDING_CLIPPED;
+ case Schedules.STATE_RECORDING_DELETED:
+ return STATE_RECORDING_DELETED;
+ case Schedules.STATE_RECORDING_CANCELED:
+ return STATE_RECORDING_CANCELED;
+ default:
+ SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state);
+ return STATE_RECORDING_NOT_STARTED;
+ }
+ }
+
+ /**
+ * Converts a @RecordingState int to string, defaulting to
+ * {@link Schedules#STATE_RECORDING_NOT_STARTED}.
+ */
+ private static String recordingState(@RecordingState int state) {
+ switch (state) {
+ case STATE_RECORDING_NOT_STARTED:
+ return Schedules.STATE_RECORDING_NOT_STARTED;
+ case STATE_RECORDING_IN_PROGRESS:
+ return Schedules.STATE_RECORDING_IN_PROGRESS;
+ case STATE_RECORDING_FINISHED:
+ return Schedules.STATE_RECORDING_FINISHED;
+ case STATE_RECORDING_FAILED:
+ return Schedules.STATE_RECORDING_FAILED;
+ case STATE_RECORDING_CLIPPED:
+ return Schedules.STATE_RECORDING_CLIPPED;
+ case STATE_RECORDING_DELETED:
+ return Schedules.STATE_RECORDING_DELETED;
+ case STATE_RECORDING_CANCELED:
+ return Schedules.STATE_RECORDING_CANCELED;
+ default:
+ SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state);
+ return Schedules.STATE_RECORDING_NOT_STARTED;
+ }
+ }
+
+ /**
+ * Checks if the {@code period} overlaps with the recording time.
+ */
+ public boolean isOverLapping(Range<Long> period) {
+ return mStartTimeMs < period.getUpper() && mEndTimeMs > period.getLower();
+ }
+
+ /**
+ * Checks if the {@code schedule} overlaps with this schedule.
+ */
+ public boolean isOverLapping(ScheduledRecording schedule) {
+ return mStartTimeMs < schedule.getEndTimeMs() && mEndTimeMs > schedule.getStartTimeMs();
+ }
+
+ @Override
+ public String toString() {
+ return "ScheduledRecording[" + mId
+ + "]"
+ + "(inputId=" + mInputId
+ + ",channelId=" + mChannelId
+ + ",programId=" + mProgramId
+ + ",programTitle=" + mProgramTitle
+ + ",type=" + mType
+ + ",startTime=" + Utils.toIsoDateTimeString(mStartTimeMs) + "(" + mStartTimeMs + ")"
+ + ",endTime=" + Utils.toIsoDateTimeString(mEndTimeMs) + "(" + mEndTimeMs + ")"
+ + ",seasonNumber=" + mSeasonNumber
+ + ",episodeNumber=" + mEpisodeNumber
+ + ",episodeTitle=" + mEpisodeTitle
+ + ",programDescription=" + mProgramDescription
+ + ",programLongDescription=" + mProgramLongDescription
+ + ",programPosterArtUri=" + mProgramPosterArtUri
+ + ",programThumbnailUri=" + mProgramThumbnailUri
+ + ",state=" + mState
+ + ",priority=" + mPriority
+ + ",seriesRecordingId=" + mSeriesRecordingId
+ + ")";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int paramInt) {
+ out.writeLong(mId);
+ out.writeLong(mPriority);
+ out.writeString(mInputId);
+ out.writeLong(mChannelId);
+ out.writeLong(mProgramId);
+ out.writeString(mProgramTitle);
+ out.writeInt(mType);
+ out.writeLong(mStartTimeMs);
+ out.writeLong(mEndTimeMs);
+ out.writeString(mSeasonNumber);
+ out.writeString(mEpisodeNumber);
+ out.writeString(mEpisodeTitle);
+ out.writeString(mProgramDescription);
+ out.writeString(mProgramLongDescription);
+ out.writeString(mProgramPosterArtUri);
+ out.writeString(mProgramThumbnailUri);
+ out.writeInt(mState);
+ out.writeLong(mSeriesRecordingId);
+ }
+
+ /**
+ * Returns {@code true} if the recording is not started yet, otherwise @{code false}.
+ */
+ public boolean isNotStarted() {
+ return mState == STATE_RECORDING_NOT_STARTED;
+ }
+
+ /**
+ * Returns {@code true} if the recording is in progress, otherwise @{code false}.
+ */
+ public boolean isInProgress() {
+ return mState == STATE_RECORDING_IN_PROGRESS;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ScheduledRecording)) {
+ return false;
+ }
+ ScheduledRecording r = (ScheduledRecording) obj;
+ return mId == r.mId
+ && mPriority == r.mPriority
+ && mChannelId == r.mChannelId
+ && mProgramId == r.mProgramId
+ && Objects.equals(mProgramTitle, r.mProgramTitle)
+ && mType == r.mType
+ && mStartTimeMs == r.mStartTimeMs
+ && mEndTimeMs == r.mEndTimeMs
+ && Objects.equals(mSeasonNumber, r.mSeasonNumber)
+ && Objects.equals(mEpisodeNumber, r.mEpisodeNumber)
+ && Objects.equals(mEpisodeTitle, r.mEpisodeTitle)
+ && Objects.equals(mProgramDescription, r.getProgramDescription())
+ && Objects.equals(mProgramLongDescription, r.getProgramLongDescription())
+ && Objects.equals(mProgramPosterArtUri, r.getProgramPosterArtUri())
+ && Objects.equals(mProgramThumbnailUri, r.getProgramThumbnailUri())
+ && mState == r.mState
+ && mSeriesRecordingId == r.mSeriesRecordingId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mPriority, mChannelId, mProgramId, mProgramTitle, mType,
+ mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber, mEpisodeTitle,
+ mProgramDescription, mProgramLongDescription, mProgramPosterArtUri,
+ mProgramThumbnailUri, mState, mSeriesRecordingId);
+ }
+
+ /**
+ * Returns an array containing all of the elements in the list.
+ */
+ public static ScheduledRecording[] toArray(Collection<ScheduledRecording> schedules) {
+ return schedules.toArray(new ScheduledRecording[schedules.size()]);
+ }
+}
diff --git a/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java
new file mode 100644
index 00000000..89533dbb
--- /dev/null
+++ b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 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.dvr.data;
+
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * A plain java object which includes the season/episode number for the series recording.
+ */
+public class SeasonEpisodeNumber {
+ public final long seriesRecordingId;
+ public final String seasonNumber;
+ public final String episodeNumber;
+
+ /**
+ * Creates a new Builder with the values set from an existing {@link ScheduledRecording}.
+ */
+ public SeasonEpisodeNumber(ScheduledRecording r) {
+ this(r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber());
+ }
+
+ public SeasonEpisodeNumber(long seriesRecordingId, String seasonNumber, String episodeNumber) {
+ this.seriesRecordingId = seriesRecordingId;
+ this.seasonNumber = seasonNumber;
+ this.episodeNumber = episodeNumber;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof SeasonEpisodeNumber)
+ || TextUtils.isEmpty(seasonNumber) || TextUtils.isEmpty(episodeNumber)) {
+ return false;
+ }
+ SeasonEpisodeNumber that = (SeasonEpisodeNumber) o;
+ return seriesRecordingId == that.seriesRecordingId
+ && Objects.equals(seasonNumber, that.seasonNumber)
+ && Objects.equals(episodeNumber, that.episodeNumber);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(seriesRecordingId, seasonNumber, episodeNumber);
+ }
+
+ @Override
+ public String toString() {
+ return "SeasonEpisodeNumber{" +
+ "seriesRecordingId=" + seriesRecordingId +
+ ", seasonNumber='" + seasonNumber +
+ ", episodeNumber=" + episodeNumber +
+ '}';
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/data/SeriesInfo.java b/src/com/android/tv/dvr/data/SeriesInfo.java
new file mode 100644
index 00000000..a0dec4a4
--- /dev/null
+++ b/src/com/android/tv/dvr/data/SeriesInfo.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 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.dvr.data;
+
+/**
+ * Series information.
+ */
+public class SeriesInfo {
+ private final String mId;
+ private final String mTitle;
+ private final String mDescription;
+ private final String mLongDescription;
+ private final int[] mCanonicalGenreIds;
+ private final String mPosterUri;
+ private final String mPhotoUri;
+
+ public SeriesInfo(String id, String title, String description, String longDescription,
+ int[] canonicalGenreIds, String posterUri, String photoUri) {
+ this.mId = id;
+ this.mTitle = title;
+ this.mDescription = description;
+ this.mLongDescription = longDescription;
+ this.mCanonicalGenreIds = canonicalGenreIds;
+ this.mPosterUri = posterUri;
+ this.mPhotoUri = photoUri;
+ }
+
+ /** Returns the ID. **/
+ public String getId() {
+ return mId;
+ }
+
+ /** Returns the title. **/
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /** Returns the description. **/
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /** Returns the description. **/
+ public String getLongDescription() {
+ return mLongDescription;
+ }
+
+ /** Returns the canonical genre IDs. **/
+ public int[] getCanonicalGenreIds() {
+ return mCanonicalGenreIds;
+ }
+
+ /** Returns the poster URI. **/
+ public String getPosterUri() {
+ return mPosterUri;
+ }
+
+ /** Returns the photo URI. **/
+ public String getPhotoUri() {
+ return mPhotoUri;
+ }
+}
diff --git a/src/com/android/tv/dvr/data/SeriesRecording.java b/src/com/android/tv/dvr/data/SeriesRecording.java
new file mode 100644
index 00000000..b7cf0f66
--- /dev/null
+++ b/src/com/android/tv/dvr/data/SeriesRecording.java
@@ -0,0 +1,756 @@
+/*
+ * 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.dvr.data;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.IntDef;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+
+import com.android.tv.data.BaseProgram;
+import com.android.tv.data.Program;
+import com.android.tv.dvr.DvrScheduleManager;
+import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
+import com.android.tv.util.Utils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * Schedules the recording of a Series of Programs.
+ *
+ * <p>
+ * Contains the data needed to create new ScheduleRecordings as the programs become available in
+ * the EPG.
+ */
+public class SeriesRecording implements Parcelable {
+ /**
+ * Indicates that the ID is not assigned yet.
+ */
+ public static final long ID_NOT_SET = 0;
+
+ /**
+ * The default priority of this recording.
+ */
+ public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {OPTION_CHANNEL_ONE, OPTION_CHANNEL_ALL})
+ public @interface ChannelOption {}
+ /**
+ * An option which indicates that the episodes in one channel are recorded.
+ */
+ public static final int OPTION_CHANNEL_ONE = 0;
+ /**
+ * An option which indicates that the episodes in all the channels are recorded.
+ */
+ public static final int OPTION_CHANNEL_ALL = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {STATE_SERIES_NORMAL, STATE_SERIES_STOPPED})
+ public @interface SeriesState {}
+
+ /**
+ * The state indicates that the series recording is a normal one.
+ */
+ public static final int STATE_SERIES_NORMAL = 0;
+
+ /**
+ * The state indicates that the series recording is stopped.
+ */
+ public static final int STATE_SERIES_STOPPED = 1;
+
+ /**
+ * Compare priority in descending order.
+ */
+ public static final Comparator<SeriesRecording> PRIORITY_COMPARATOR =
+ new Comparator<SeriesRecording>() {
+ @Override
+ public int compare(SeriesRecording lhs, SeriesRecording rhs) {
+ int value = Long.compare(rhs.mPriority, lhs.mPriority);
+ if (value == 0) {
+ // New recording has the higher priority.
+ value = Long.compare(rhs.mId, lhs.mId);
+ }
+ return value;
+ }
+ };
+
+ /**
+ * Compare ID in ascending order.
+ */
+ public static final Comparator<SeriesRecording> ID_COMPARATOR =
+ new Comparator<SeriesRecording>() {
+ @Override
+ public int compare(SeriesRecording lhs, SeriesRecording rhs) {
+ return Long.compare(lhs.mId, rhs.mId);
+ }
+ };
+
+ /**
+ * Creates a new Builder with the values set from the series information of {@link BaseProgram}.
+ */
+ public static Builder builder(String inputId, BaseProgram p) {
+ return new Builder()
+ .setInputId(inputId)
+ .setSeriesId(p.getSeriesId())
+ .setChannelId(p.getChannelId())
+ .setTitle(p.getTitle())
+ .setDescription(p.getDescription())
+ .setLongDescription(p.getLongDescription())
+ .setCanonicalGenreIds(p.getCanonicalGenreIds())
+ .setPosterUri(p.getPosterArtUri())
+ .setPhotoUri(p.getThumbnailUri());
+ }
+
+ /**
+ * Creates a new Builder with the values set from an existing {@link SeriesRecording}.
+ */
+ @VisibleForTesting
+ public static Builder buildFrom(SeriesRecording r) {
+ return new Builder()
+ .setId(r.mId)
+ .setInputId(r.getInputId())
+ .setChannelId(r.getChannelId())
+ .setPriority(r.getPriority())
+ .setTitle(r.getTitle())
+ .setDescription(r.getDescription())
+ .setLongDescription(r.getLongDescription())
+ .setSeriesId(r.getSeriesId())
+ .setStartFromEpisode(r.getStartFromEpisode())
+ .setStartFromSeason(r.getStartFromSeason())
+ .setChannelOption(r.getChannelOption())
+ .setCanonicalGenreIds(r.getCanonicalGenreIds())
+ .setPosterUri(r.getPosterUri())
+ .setPhotoUri(r.getPhotoUri())
+ .setState(r.getState());
+ }
+
+ /**
+ * Use this projection if you want to create {@link SeriesRecording} object using
+ * {@link #fromCursor}.
+ */
+ public static final String[] PROJECTION = {
+ // Columns must match what is read in fromCursor()
+ SeriesRecordings._ID,
+ SeriesRecordings.COLUMN_INPUT_ID,
+ SeriesRecordings.COLUMN_CHANNEL_ID,
+ SeriesRecordings.COLUMN_PRIORITY,
+ SeriesRecordings.COLUMN_TITLE,
+ SeriesRecordings.COLUMN_SHORT_DESCRIPTION,
+ SeriesRecordings.COLUMN_LONG_DESCRIPTION,
+ SeriesRecordings.COLUMN_SERIES_ID,
+ SeriesRecordings.COLUMN_START_FROM_EPISODE,
+ SeriesRecordings.COLUMN_START_FROM_SEASON,
+ SeriesRecordings.COLUMN_CHANNEL_OPTION,
+ SeriesRecordings.COLUMN_CANONICAL_GENRE,
+ SeriesRecordings.COLUMN_POSTER_URI,
+ SeriesRecordings.COLUMN_PHOTO_URI,
+ SeriesRecordings.COLUMN_STATE
+ };
+ /**
+ * Creates {@link SeriesRecording} object from the given {@link Cursor}.
+ */
+ public static SeriesRecording fromCursor(Cursor c) {
+ int index = -1;
+ return new Builder()
+ .setId(c.getLong(++index))
+ .setInputId(c.getString(++index))
+ .setChannelId(c.getLong(++index))
+ .setPriority(c.getLong(++index))
+ .setTitle(c.getString(++index))
+ .setDescription(c.getString(++index))
+ .setLongDescription(c.getString(++index))
+ .setSeriesId(c.getString(++index))
+ .setStartFromEpisode(c.getInt(++index))
+ .setStartFromSeason(c.getInt(++index))
+ .setChannelOption(channelOption(c.getString(++index)))
+ .setCanonicalGenreIds(c.getString(++index))
+ .setPosterUri(c.getString(++index))
+ .setPhotoUri(c.getString(++index))
+ .setState(seriesRecordingState(c.getString(++index)))
+ .build();
+ }
+
+ /**
+ * Returns the ContentValues with keys as the columns specified in {@link SeriesRecordings}
+ * and the values from {@code r}.
+ */
+ public static ContentValues toContentValues(SeriesRecording r) {
+ ContentValues values = new ContentValues();
+ if (r.getId() != ID_NOT_SET) {
+ values.put(SeriesRecordings._ID, r.getId());
+ } else {
+ values.putNull(SeriesRecordings._ID);
+ }
+ values.put(SeriesRecordings.COLUMN_INPUT_ID, r.getInputId());
+ values.put(SeriesRecordings.COLUMN_CHANNEL_ID, r.getChannelId());
+ values.put(SeriesRecordings.COLUMN_PRIORITY, r.getPriority());
+ values.put(SeriesRecordings.COLUMN_TITLE, r.getTitle());
+ values.put(SeriesRecordings.COLUMN_SHORT_DESCRIPTION, r.getDescription());
+ values.put(SeriesRecordings.COLUMN_LONG_DESCRIPTION, r.getLongDescription());
+ values.put(SeriesRecordings.COLUMN_SERIES_ID, r.getSeriesId());
+ values.put(SeriesRecordings.COLUMN_START_FROM_EPISODE, r.getStartFromEpisode());
+ values.put(SeriesRecordings.COLUMN_START_FROM_SEASON, r.getStartFromSeason());
+ values.put(SeriesRecordings.COLUMN_CHANNEL_OPTION,
+ channelOption(r.getChannelOption()));
+ values.put(SeriesRecordings.COLUMN_CANONICAL_GENRE,
+ Utils.getCanonicalGenre(r.getCanonicalGenreIds()));
+ values.put(SeriesRecordings.COLUMN_POSTER_URI, r.getPosterUri());
+ values.put(SeriesRecordings.COLUMN_PHOTO_URI, r.getPhotoUri());
+ values.put(SeriesRecordings.COLUMN_STATE, seriesRecordingState(r.getState()));
+ return values;
+ }
+
+ private static String channelOption(@ChannelOption int option) {
+ switch (option) {
+ case OPTION_CHANNEL_ONE:
+ return SeriesRecordings.OPTION_CHANNEL_ONE;
+ case OPTION_CHANNEL_ALL:
+ return SeriesRecordings.OPTION_CHANNEL_ALL;
+ }
+ return SeriesRecordings.OPTION_CHANNEL_ONE;
+ }
+
+ @ChannelOption private static int channelOption(String option) {
+ switch (option) {
+ case SeriesRecordings.OPTION_CHANNEL_ONE:
+ return OPTION_CHANNEL_ONE;
+ case SeriesRecordings.OPTION_CHANNEL_ALL:
+ return OPTION_CHANNEL_ALL;
+ }
+ return OPTION_CHANNEL_ONE;
+ }
+
+ private static String seriesRecordingState(@SeriesState int state) {
+ switch (state) {
+ case STATE_SERIES_NORMAL:
+ return SeriesRecordings.STATE_SERIES_NORMAL;
+ case STATE_SERIES_STOPPED:
+ return SeriesRecordings.STATE_SERIES_STOPPED;
+ }
+ return SeriesRecordings.STATE_SERIES_NORMAL;
+ }
+
+ @SeriesState private static int seriesRecordingState(String state) {
+ switch (state) {
+ case SeriesRecordings.STATE_SERIES_NORMAL:
+ return STATE_SERIES_NORMAL;
+ case SeriesRecordings.STATE_SERIES_STOPPED:
+ return STATE_SERIES_STOPPED;
+ }
+ return STATE_SERIES_NORMAL;
+ }
+
+ /**
+ * Builder for {@link SeriesRecording}.
+ */
+ public static class Builder {
+ private long mId = ID_NOT_SET;
+ private long mPriority = DvrScheduleManager.DEFAULT_SERIES_PRIORITY;
+ private String mTitle;
+ private String mDescription;
+ private String mLongDescription;
+ private String mInputId;
+ private long mChannelId;
+ private String mSeriesId;
+ private int mStartFromSeason = SeriesRecordings.THE_BEGINNING;
+ private int mStartFromEpisode = SeriesRecordings.THE_BEGINNING;
+ private int mChannelOption = OPTION_CHANNEL_ONE;
+ private int[] mCanonicalGenreIds;
+ private String mPosterUri;
+ private String mPhotoUri;
+ private int mState = SeriesRecording.STATE_SERIES_NORMAL;
+
+ /**
+ * @see #getId()
+ */
+ public Builder setId(long id) {
+ mId = id;
+ return this;
+ }
+
+ /**
+ * @see #getPriority() ()
+ */
+ public Builder setPriority(long priority) {
+ mPriority = priority;
+ return this;
+ }
+
+ /**
+ * @see #getTitle()
+ */
+ public Builder setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * @see #getDescription()
+ */
+ public Builder setDescription(String description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * @see #getLongDescription()
+ */
+ public Builder setLongDescription(String longDescription) {
+ mLongDescription = longDescription;
+ return this;
+ }
+
+ /**
+ * @see #getInputId()
+ */
+ public Builder setInputId(String inputId) {
+ mInputId = inputId;
+ return this;
+ }
+
+ /**
+ * @see #getChannelId()
+ */
+ public Builder setChannelId(long channelId) {
+ mChannelId = channelId;
+ return this;
+ }
+
+ /**
+ * @see #getSeriesId()
+ */
+ public Builder setSeriesId(String seriesId) {
+ mSeriesId = seriesId;
+ return this;
+ }
+
+ /**
+ * @see #getStartFromSeason()
+ */
+ public Builder setStartFromSeason(int startFromSeason) {
+ mStartFromSeason = startFromSeason;
+ return this;
+ }
+
+ /**
+ * @see #getChannelOption()
+ */
+ public Builder setChannelOption(@ChannelOption int option) {
+ mChannelOption = option;
+ return this;
+ }
+
+ /**
+ * @see #getStartFromEpisode()
+ */
+ public Builder setStartFromEpisode(int startFromEpisode) {
+ mStartFromEpisode = startFromEpisode;
+ return this;
+ }
+
+ /**
+ * @see #getCanonicalGenreIds()
+ */
+ public Builder setCanonicalGenreIds(String genres) {
+ mCanonicalGenreIds = Utils.getCanonicalGenreIds(genres);
+ return this;
+ }
+
+ /**
+ * @see #getCanonicalGenreIds()
+ */
+ public Builder setCanonicalGenreIds(int[] canonicalGenreIds) {
+ mCanonicalGenreIds = canonicalGenreIds;
+ return this;
+ }
+
+ /**
+ * @see #getPosterUri()
+ */
+ public Builder setPosterUri(String posterUri) {
+ mPosterUri = posterUri;
+ return this;
+ }
+
+ /**
+ * @see #getPhotoUri()
+ */
+ public Builder setPhotoUri(String photoUri) {
+ mPhotoUri = photoUri;
+ return this;
+ }
+
+ /**
+ * @see #getState()
+ */
+ public Builder setState(@SeriesState int state) {
+ mState = state;
+ return this;
+ }
+
+ /**
+ * Creates a new {@link SeriesRecording}.
+ */
+ public SeriesRecording build() {
+ return new SeriesRecording(mId, mPriority, mTitle, mDescription, mLongDescription,
+ mInputId, mChannelId, mSeriesId, mStartFromSeason, mStartFromEpisode,
+ mChannelOption, mCanonicalGenreIds, mPosterUri, mPhotoUri, mState);
+ }
+ }
+
+ public static SeriesRecording fromParcel(Parcel in) {
+ return new Builder()
+ .setId(in.readLong())
+ .setPriority(in.readLong())
+ .setTitle(in.readString())
+ .setDescription(in.readString())
+ .setLongDescription(in.readString())
+ .setInputId(in.readString())
+ .setChannelId(in.readLong())
+ .setSeriesId(in.readString())
+ .setStartFromSeason(in.readInt())
+ .setStartFromEpisode(in.readInt())
+ .setChannelOption(in.readInt())
+ .setCanonicalGenreIds(in.createIntArray())
+ .setPosterUri(in.readString())
+ .setPhotoUri(in.readString())
+ .setState(in.readInt())
+ .build();
+ }
+
+ public static final Parcelable.Creator<SeriesRecording> CREATOR =
+ new Parcelable.Creator<SeriesRecording>() {
+ @Override
+ public SeriesRecording createFromParcel(Parcel in) {
+ return SeriesRecording.fromParcel(in);
+ }
+
+ @Override
+ public SeriesRecording[] newArray(int size) {
+ return new SeriesRecording[size];
+ }
+ };
+
+ private long mId;
+ private final long mPriority;
+ private final String mTitle;
+ private final String mDescription;
+ private final String mLongDescription;
+ private final String mInputId;
+ private final long mChannelId;
+ private final String mSeriesId;
+ private final int mStartFromSeason;
+ private final int mStartFromEpisode;
+ @ChannelOption private final int mChannelOption;
+ private final int[] mCanonicalGenreIds;
+ private final String mPosterUri;
+ private final String mPhotoUri;
+ @SeriesState private int mState;
+
+ /**
+ * The input id of this SeriesRecording.
+ */
+ public String getInputId() {
+ return mInputId;
+ }
+
+ /**
+ * The channelId to match. The channel ID might not be valid when the channel option is "ALL".
+ */
+ public long getChannelId() {
+ return mChannelId;
+ }
+
+ /**
+ * The id of this SeriesRecording.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Sets the ID.
+ */
+ public void setId(long id) {
+ mId = id;
+ }
+
+ /**
+ * The priority of this recording.
+ *
+ * <p> The highest number is recorded first. If there is a tie in mPriority then the higher mId
+ * wins.
+ */
+ public long getPriority() {
+ return mPriority;
+ }
+
+ /**
+ * The series title.
+ */
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * The series description.
+ */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * The long series description.
+ */
+ public String getLongDescription() {
+ return mLongDescription;
+ }
+
+ /**
+ * SeriesId when not null is used to match programs instead of using title and channelId.
+ *
+ * <p>SeriesId is an opaque but stable string.
+ */
+ public String getSeriesId() {
+ return mSeriesId;
+ }
+
+ /**
+ * If not == {@link SeriesRecordings#THE_BEGINNING} and seasonNumber == startFromSeason then
+ * only record episodes with a episodeNumber >= this
+ */
+ public int getStartFromEpisode() {
+ return mStartFromEpisode;
+ }
+
+ /**
+ * If not == {@link SeriesRecordings#THE_BEGINNING} then only record episodes with a
+ * seasonNumber >= this
+ */
+ public int getStartFromSeason() {
+ return mStartFromSeason;
+ }
+
+ /**
+ * Returns the channel recording option.
+ */
+ @ChannelOption public int getChannelOption() {
+ return mChannelOption;
+ }
+
+ /**
+ * Returns the canonical genre ID's.
+ */
+ public int[] getCanonicalGenreIds() {
+ return mCanonicalGenreIds;
+ }
+
+ /**
+ * Returns the poster URI.
+ */
+ public String getPosterUri() {
+ return mPosterUri;
+ }
+
+ /**
+ * Returns the photo URI.
+ */
+ public String getPhotoUri() {
+ return mPhotoUri;
+ }
+
+ /**
+ * Returns the state of series recording.
+ */
+ @SeriesState public int getState() {
+ return mState;
+ }
+
+ /**
+ * Checks whether the series recording is stopped or not.
+ */
+ public boolean isStopped() {
+ return mState == STATE_SERIES_STOPPED;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SeriesRecording)) return false;
+ SeriesRecording that = (SeriesRecording) o;
+ return mPriority == that.mPriority
+ && mChannelId == that.mChannelId
+ && mStartFromSeason == that.mStartFromSeason
+ && mStartFromEpisode == that.mStartFromEpisode
+ && Objects.equals(mId, that.mId)
+ && Objects.equals(mTitle, that.mTitle)
+ && Objects.equals(mDescription, that.mDescription)
+ && Objects.equals(mLongDescription, that.mLongDescription)
+ && Objects.equals(mSeriesId, that.mSeriesId)
+ && mChannelOption == that.mChannelOption
+ && Arrays.equals(mCanonicalGenreIds, that.mCanonicalGenreIds)
+ && Objects.equals(mPosterUri, that.mPosterUri)
+ && Objects.equals(mPhotoUri, that.mPhotoUri)
+ && mState == that.mState;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPriority, mChannelId, mStartFromSeason, mStartFromEpisode, mId,
+ mTitle, mDescription, mLongDescription, mSeriesId, mChannelOption,
+ mCanonicalGenreIds, mPosterUri, mPhotoUri, mState);
+ }
+
+ @Override
+ public String toString() {
+ return "SeriesRecording{" +
+ "inputId=" + mInputId +
+ ", channelId=" + mChannelId +
+ ", id='" + mId + '\'' +
+ ", priority=" + mPriority +
+ ", title='" + mTitle + '\'' +
+ ", description='" + mDescription + '\'' +
+ ", longDescription='" + mLongDescription + '\'' +
+ ", startFromSeason=" + mStartFromSeason +
+ ", startFromEpisode=" + mStartFromEpisode +
+ ", channelOption=" + mChannelOption +
+ ", canonicalGenreIds=" + Arrays.toString(mCanonicalGenreIds) +
+ ", posterUri=" + mPosterUri +
+ ", photoUri=" + mPhotoUri +
+ ", state=" + mState +
+ '}';
+ }
+
+ private SeriesRecording(long id, long priority, String title, String description,
+ String longDescription, String inputId, long channelId, String seriesId,
+ int startFromSeason, int startFromEpisode, int channelOption, int[] canonicalGenreIds,
+ String posterUri, String photoUri, int state) {
+ this.mId = id;
+ this.mPriority = priority;
+ this.mTitle = title;
+ this.mDescription = description;
+ this.mLongDescription = longDescription;
+ this.mInputId = inputId;
+ this.mChannelId = channelId;
+ this.mSeriesId = seriesId;
+ this.mStartFromSeason = startFromSeason;
+ this.mStartFromEpisode = startFromEpisode;
+ this.mChannelOption = channelOption;
+ this.mCanonicalGenreIds = canonicalGenreIds;
+ this.mPosterUri = posterUri;
+ this.mPhotoUri = photoUri;
+ this.mState = state;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int paramInt) {
+ out.writeLong(mId);
+ out.writeLong(mPriority);
+ out.writeString(mTitle);
+ out.writeString(mDescription);
+ out.writeString(mLongDescription);
+ out.writeString(mInputId);
+ out.writeLong(mChannelId);
+ out.writeString(mSeriesId);
+ out.writeInt(mStartFromSeason);
+ out.writeInt(mStartFromEpisode);
+ out.writeInt(mChannelOption);
+ out.writeIntArray(mCanonicalGenreIds);
+ out.writeString(mPosterUri);
+ out.writeString(mPhotoUri);
+ out.writeInt(mState);
+ }
+
+ /**
+ * Returns an array containing all of the elements in the list.
+ */
+ public static SeriesRecording[] toArray(Collection<SeriesRecording> series) {
+ return series.toArray(new SeriesRecording[series.size()]);
+ }
+
+ /**
+ * Returns {@code true} if the {@code program} is part of the series and meets the season and
+ * episode constraints.
+ */
+ public boolean matchProgram(Program program) {
+ return matchProgram(program, mChannelOption);
+ }
+
+ /**
+ * Returns {@code true} if the {@code program} is part of the series and meets the season and
+ * episode constraints. It checks the channel option only if {@code checkChannelOption} is
+ * {@code true}.
+ */
+ public boolean matchProgram(Program program, @ChannelOption int channelOption) {
+ String seriesId = program.getSeriesId();
+ long channelId = program.getChannelId();
+ String seasonNumber = program.getSeasonNumber();
+ String episodeNumber = program.getEpisodeNumber();
+ if (!mSeriesId.equals(seriesId) || (channelOption == SeriesRecording.OPTION_CHANNEL_ONE
+ && mChannelId != channelId)) {
+ return false;
+ }
+ // Season number and episode number matches if
+ // start_season_number < program_season_number
+ // || (start_season_number == program_season_number
+ // && start_episode_number <= program_episode_number).
+ if (mStartFromSeason == SeriesRecordings.THE_BEGINNING
+ || TextUtils.isEmpty(seasonNumber)) {
+ return true;
+ } else {
+ int intSeasonNumber;
+ try {
+ intSeasonNumber = Integer.valueOf(seasonNumber);
+ } catch (NumberFormatException e) {
+ return true;
+ }
+ if (intSeasonNumber > mStartFromSeason) {
+ return true;
+ } else if (intSeasonNumber < mStartFromSeason) {
+ return false;
+ }
+ }
+ if (mStartFromEpisode == SeriesRecordings.THE_BEGINNING
+ || TextUtils.isEmpty(episodeNumber)) {
+ return true;
+ } else {
+ int intEpisodeNumber;
+ try {
+ intEpisodeNumber = Integer.valueOf(episodeNumber);
+ } catch (NumberFormatException e) {
+ return true;
+ }
+ return intEpisodeNumber >= mStartFromEpisode;
+ }
+ }
+}