1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.tv.dvr.data;
18
19import android.content.ContentValues;
20import android.database.Cursor;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.support.annotation.IntDef;
24import android.support.annotation.VisibleForTesting;
25import android.text.TextUtils;
26
27import com.android.tv.data.BaseProgram;
28import com.android.tv.data.Program;
29import com.android.tv.dvr.DvrScheduleManager;
30import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
31import com.android.tv.util.Utils;
32
33import java.lang.annotation.Retention;
34import java.lang.annotation.RetentionPolicy;
35import java.util.Arrays;
36import java.util.Collection;
37import java.util.Comparator;
38import java.util.Objects;
39
40/**
41 * Schedules the recording of a Series of Programs.
42 *
43 * <p>
44 * Contains the data needed to create new ScheduleRecordings as the programs become available in
45 * the EPG.
46 */
47public class SeriesRecording implements Parcelable {
48    /**
49     * Indicates that the ID is not assigned yet.
50     */
51    public static final long ID_NOT_SET = 0;
52
53    /**
54     * The default priority of this recording.
55     */
56    public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1;
57
58    @Retention(RetentionPolicy.SOURCE)
59    @IntDef(flag = true,
60            value = {OPTION_CHANNEL_ONE, OPTION_CHANNEL_ALL})
61    public @interface ChannelOption {}
62    /**
63     * An option which indicates that the episodes in one channel are recorded.
64     */
65    public static final int OPTION_CHANNEL_ONE = 0;
66    /**
67     * An option which indicates that the episodes in all the channels are recorded.
68     */
69    public static final int OPTION_CHANNEL_ALL = 1;
70
71    @Retention(RetentionPolicy.SOURCE)
72    @IntDef(flag = true,
73            value = {STATE_SERIES_NORMAL, STATE_SERIES_STOPPED})
74    public @interface SeriesState {}
75
76    /**
77     * The state indicates that the series recording is a normal one.
78     */
79    public static final int STATE_SERIES_NORMAL = 0;
80
81    /**
82     * The state indicates that the series recording is stopped.
83     */
84    public static final int STATE_SERIES_STOPPED = 1;
85
86    /**
87     * Compare priority in descending order.
88     */
89    public static final Comparator<SeriesRecording> PRIORITY_COMPARATOR =
90            new Comparator<SeriesRecording>() {
91        @Override
92        public int compare(SeriesRecording lhs, SeriesRecording rhs) {
93            int value = Long.compare(rhs.mPriority, lhs.mPriority);
94            if (value == 0) {
95                // New recording has the higher priority.
96                value = Long.compare(rhs.mId, lhs.mId);
97            }
98            return value;
99        }
100    };
101
102    /**
103     * Compare ID in ascending order.
104     */
105    public static final Comparator<SeriesRecording> ID_COMPARATOR =
106            new Comparator<SeriesRecording>() {
107                @Override
108                public int compare(SeriesRecording lhs, SeriesRecording rhs) {
109                    return Long.compare(lhs.mId, rhs.mId);
110                }
111            };
112
113    /**
114     * Creates a new Builder with the values set from the series information of {@link BaseProgram}.
115     */
116    public static Builder builder(String inputId, BaseProgram p) {
117        return new Builder()
118                .setInputId(inputId)
119                .setSeriesId(p.getSeriesId())
120                .setChannelId(p.getChannelId())
121                .setTitle(p.getTitle())
122                .setDescription(p.getDescription())
123                .setLongDescription(p.getLongDescription())
124                .setCanonicalGenreIds(p.getCanonicalGenreIds())
125                .setPosterUri(p.getPosterArtUri())
126                .setPhotoUri(p.getThumbnailUri());
127    }
128
129    /**
130     * Creates a new Builder with the values set from an existing {@link SeriesRecording}.
131     */
132    public static Builder buildFrom(SeriesRecording r) {
133        return new Builder()
134                .setId(r.mId)
135                .setInputId(r.getInputId())
136                .setChannelId(r.getChannelId())
137                .setPriority(r.getPriority())
138                .setTitle(r.getTitle())
139                .setDescription(r.getDescription())
140                .setLongDescription(r.getLongDescription())
141                .setSeriesId(r.getSeriesId())
142                .setStartFromEpisode(r.getStartFromEpisode())
143                .setStartFromSeason(r.getStartFromSeason())
144                .setChannelOption(r.getChannelOption())
145                .setCanonicalGenreIds(r.getCanonicalGenreIds())
146                .setPosterUri(r.getPosterUri())
147                .setPhotoUri(r.getPhotoUri())
148                .setState(r.getState());
149    }
150
151    /**
152     * Use this projection if you want to create {@link SeriesRecording} object using
153     * {@link #fromCursor}.
154     */
155    public static final String[] PROJECTION = {
156            // Columns must match what is read in fromCursor()
157            SeriesRecordings._ID,
158            SeriesRecordings.COLUMN_INPUT_ID,
159            SeriesRecordings.COLUMN_CHANNEL_ID,
160            SeriesRecordings.COLUMN_PRIORITY,
161            SeriesRecordings.COLUMN_TITLE,
162            SeriesRecordings.COLUMN_SHORT_DESCRIPTION,
163            SeriesRecordings.COLUMN_LONG_DESCRIPTION,
164            SeriesRecordings.COLUMN_SERIES_ID,
165            SeriesRecordings.COLUMN_START_FROM_EPISODE,
166            SeriesRecordings.COLUMN_START_FROM_SEASON,
167            SeriesRecordings.COLUMN_CHANNEL_OPTION,
168            SeriesRecordings.COLUMN_CANONICAL_GENRE,
169            SeriesRecordings.COLUMN_POSTER_URI,
170            SeriesRecordings.COLUMN_PHOTO_URI,
171            SeriesRecordings.COLUMN_STATE
172    };
173    /**
174     * Creates {@link SeriesRecording} object from the given {@link Cursor}.
175     */
176    public static SeriesRecording fromCursor(Cursor c) {
177        int index = -1;
178        return new Builder()
179                .setId(c.getLong(++index))
180                .setInputId(c.getString(++index))
181                .setChannelId(c.getLong(++index))
182                .setPriority(c.getLong(++index))
183                .setTitle(c.getString(++index))
184                .setDescription(c.getString(++index))
185                .setLongDescription(c.getString(++index))
186                .setSeriesId(c.getString(++index))
187                .setStartFromEpisode(c.getInt(++index))
188                .setStartFromSeason(c.getInt(++index))
189                .setChannelOption(channelOption(c.getString(++index)))
190                .setCanonicalGenreIds(c.getString(++index))
191                .setPosterUri(c.getString(++index))
192                .setPhotoUri(c.getString(++index))
193                .setState(seriesRecordingState(c.getString(++index)))
194                .build();
195    }
196
197    /**
198     * Returns the ContentValues with keys as the columns specified in {@link SeriesRecordings}
199     * and the values from {@code r}.
200     */
201    public static ContentValues toContentValues(SeriesRecording r) {
202        ContentValues values = new ContentValues();
203        if (r.getId() != ID_NOT_SET) {
204            values.put(SeriesRecordings._ID, r.getId());
205        } else {
206            values.putNull(SeriesRecordings._ID);
207        }
208        values.put(SeriesRecordings.COLUMN_INPUT_ID, r.getInputId());
209        values.put(SeriesRecordings.COLUMN_CHANNEL_ID, r.getChannelId());
210        values.put(SeriesRecordings.COLUMN_PRIORITY, r.getPriority());
211        values.put(SeriesRecordings.COLUMN_TITLE, r.getTitle());
212        values.put(SeriesRecordings.COLUMN_SHORT_DESCRIPTION, r.getDescription());
213        values.put(SeriesRecordings.COLUMN_LONG_DESCRIPTION, r.getLongDescription());
214        values.put(SeriesRecordings.COLUMN_SERIES_ID, r.getSeriesId());
215        values.put(SeriesRecordings.COLUMN_START_FROM_EPISODE, r.getStartFromEpisode());
216        values.put(SeriesRecordings.COLUMN_START_FROM_SEASON, r.getStartFromSeason());
217        values.put(SeriesRecordings.COLUMN_CHANNEL_OPTION,
218                channelOption(r.getChannelOption()));
219        values.put(SeriesRecordings.COLUMN_CANONICAL_GENRE,
220                Utils.getCanonicalGenre(r.getCanonicalGenreIds()));
221        values.put(SeriesRecordings.COLUMN_POSTER_URI, r.getPosterUri());
222        values.put(SeriesRecordings.COLUMN_PHOTO_URI, r.getPhotoUri());
223        values.put(SeriesRecordings.COLUMN_STATE, seriesRecordingState(r.getState()));
224        return values;
225    }
226
227    private static String channelOption(@ChannelOption int option) {
228        switch (option) {
229            case OPTION_CHANNEL_ONE:
230                return SeriesRecordings.OPTION_CHANNEL_ONE;
231            case OPTION_CHANNEL_ALL:
232                return SeriesRecordings.OPTION_CHANNEL_ALL;
233        }
234        return SeriesRecordings.OPTION_CHANNEL_ONE;
235    }
236
237    @ChannelOption private static int channelOption(String option) {
238        switch (option) {
239            case SeriesRecordings.OPTION_CHANNEL_ONE:
240                return OPTION_CHANNEL_ONE;
241            case SeriesRecordings.OPTION_CHANNEL_ALL:
242                return OPTION_CHANNEL_ALL;
243        }
244        return OPTION_CHANNEL_ONE;
245    }
246
247    private static String seriesRecordingState(@SeriesState int state) {
248        switch (state) {
249            case STATE_SERIES_NORMAL:
250                return SeriesRecordings.STATE_SERIES_NORMAL;
251            case STATE_SERIES_STOPPED:
252                return SeriesRecordings.STATE_SERIES_STOPPED;
253        }
254        return SeriesRecordings.STATE_SERIES_NORMAL;
255    }
256
257    @SeriesState private static int seriesRecordingState(String state) {
258        switch (state) {
259            case SeriesRecordings.STATE_SERIES_NORMAL:
260                return STATE_SERIES_NORMAL;
261            case SeriesRecordings.STATE_SERIES_STOPPED:
262                return STATE_SERIES_STOPPED;
263        }
264        return STATE_SERIES_NORMAL;
265    }
266
267    /**
268     * Builder for {@link SeriesRecording}.
269     */
270    public static class Builder {
271        private long mId = ID_NOT_SET;
272        private long mPriority = DvrScheduleManager.DEFAULT_SERIES_PRIORITY;
273        private String mTitle;
274        private String mDescription;
275        private String mLongDescription;
276        private String mInputId;
277        private long mChannelId;
278        private String mSeriesId;
279        private int mStartFromSeason = SeriesRecordings.THE_BEGINNING;
280        private int mStartFromEpisode = SeriesRecordings.THE_BEGINNING;
281        private int mChannelOption = OPTION_CHANNEL_ONE;
282        private int[] mCanonicalGenreIds;
283        private String mPosterUri;
284        private String mPhotoUri;
285        private int mState = SeriesRecording.STATE_SERIES_NORMAL;
286
287        /**
288         * @see #getId()
289         */
290        public Builder setId(long id) {
291            mId = id;
292            return this;
293        }
294
295        /**
296         * @see #getPriority() ()
297         */
298        public Builder setPriority(long priority) {
299            mPriority = priority;
300            return this;
301        }
302
303        /**
304         * @see #getTitle()
305         */
306        public Builder setTitle(String title) {
307            mTitle = title;
308            return this;
309        }
310
311        /**
312         * @see #getDescription()
313         */
314        public Builder setDescription(String description) {
315            mDescription = description;
316            return this;
317        }
318
319        /**
320         * @see #getLongDescription()
321         */
322        public Builder setLongDescription(String longDescription) {
323            mLongDescription = longDescription;
324            return this;
325        }
326
327        /**
328         * @see #getInputId()
329         */
330        public Builder setInputId(String inputId) {
331            mInputId = inputId;
332            return this;
333        }
334
335        /**
336         * @see #getChannelId()
337         */
338        public Builder setChannelId(long channelId) {
339            mChannelId = channelId;
340            return this;
341        }
342
343        /**
344         * @see #getSeriesId()
345         */
346        public Builder setSeriesId(String seriesId) {
347            mSeriesId = seriesId;
348            return this;
349        }
350
351        /**
352         * @see #getStartFromSeason()
353         */
354        public Builder setStartFromSeason(int startFromSeason) {
355            mStartFromSeason = startFromSeason;
356            return this;
357        }
358
359        /**
360         * @see #getChannelOption()
361         */
362        public Builder setChannelOption(@ChannelOption int option) {
363            mChannelOption = option;
364            return this;
365        }
366
367        /**
368         * @see #getStartFromEpisode()
369         */
370        public Builder setStartFromEpisode(int startFromEpisode) {
371            mStartFromEpisode = startFromEpisode;
372            return this;
373        }
374
375        /**
376         * @see #getCanonicalGenreIds()
377         */
378        public Builder setCanonicalGenreIds(String genres) {
379            mCanonicalGenreIds = Utils.getCanonicalGenreIds(genres);
380            return this;
381        }
382
383        /**
384         * @see #getCanonicalGenreIds()
385         */
386        public Builder setCanonicalGenreIds(int[] canonicalGenreIds) {
387            mCanonicalGenreIds = canonicalGenreIds;
388            return this;
389        }
390
391        /**
392         * @see #getPosterUri()
393         */
394        public Builder setPosterUri(String posterUri) {
395            mPosterUri = posterUri;
396            return this;
397        }
398
399        /**
400         * @see #getPhotoUri()
401         */
402        public Builder setPhotoUri(String photoUri) {
403            mPhotoUri = photoUri;
404            return this;
405        }
406
407        /**
408         * @see #getState()
409         */
410        public Builder setState(@SeriesState int state) {
411            mState = state;
412            return this;
413        }
414
415        /**
416         * Creates a new {@link SeriesRecording}.
417         */
418        public SeriesRecording build() {
419            return new SeriesRecording(mId, mPriority, mTitle, mDescription, mLongDescription,
420                    mInputId, mChannelId, mSeriesId, mStartFromSeason, mStartFromEpisode,
421                    mChannelOption, mCanonicalGenreIds, mPosterUri, mPhotoUri, mState);
422        }
423    }
424
425    public static SeriesRecording fromParcel(Parcel in) {
426        return new Builder()
427                .setId(in.readLong())
428                .setPriority(in.readLong())
429                .setTitle(in.readString())
430                .setDescription(in.readString())
431                .setLongDescription(in.readString())
432                .setInputId(in.readString())
433                .setChannelId(in.readLong())
434                .setSeriesId(in.readString())
435                .setStartFromSeason(in.readInt())
436                .setStartFromEpisode(in.readInt())
437                .setChannelOption(in.readInt())
438                .setCanonicalGenreIds(in.createIntArray())
439                .setPosterUri(in.readString())
440                .setPhotoUri(in.readString())
441                .setState(in.readInt())
442                .build();
443    }
444
445    public static final Parcelable.Creator<SeriesRecording> CREATOR =
446            new Parcelable.Creator<SeriesRecording>() {
447        @Override
448        public SeriesRecording createFromParcel(Parcel in) {
449          return SeriesRecording.fromParcel(in);
450        }
451
452        @Override
453        public SeriesRecording[] newArray(int size) {
454          return new SeriesRecording[size];
455        }
456    };
457
458    private long mId;
459    private final long mPriority;
460    private final String mTitle;
461    private final String mDescription;
462    private final String mLongDescription;
463    private final String mInputId;
464    private final long mChannelId;
465    private final String mSeriesId;
466    private final int mStartFromSeason;
467    private final int mStartFromEpisode;
468    @ChannelOption private final int mChannelOption;
469    private final int[] mCanonicalGenreIds;
470    private final String mPosterUri;
471    private final String mPhotoUri;
472    @SeriesState private int mState;
473
474    /**
475     * The input id of this SeriesRecording.
476     */
477    public String getInputId() {
478        return mInputId;
479    }
480
481    /**
482     * The channelId to match. The channel ID might not be valid when the channel option is "ALL".
483     */
484    public long getChannelId() {
485        return mChannelId;
486    }
487
488    /**
489     * The id of this SeriesRecording.
490     */
491    public long getId() {
492        return mId;
493    }
494
495    /**
496     * Sets the ID.
497     */
498    public void setId(long id) {
499        mId = id;
500    }
501
502    /**
503     * The priority of this recording.
504     *
505     * <p> The highest number is recorded first. If there is a tie in mPriority then the higher mId
506     * wins.
507     */
508    public long getPriority() {
509        return mPriority;
510    }
511
512    /**
513     * The series title.
514     */
515    public String getTitle() {
516        return mTitle;
517    }
518
519    /**
520     * The series description.
521     */
522    public String getDescription() {
523        return mDescription;
524    }
525
526    /**
527     * The long series description.
528     */
529    public String getLongDescription() {
530        return mLongDescription;
531    }
532
533    /**
534     * SeriesId when not null is used to match programs instead of using title and channelId.
535     *
536     * <p>SeriesId is an opaque but stable string.
537     */
538    public String getSeriesId() {
539        return mSeriesId;
540    }
541
542    /**
543     * If not == {@link SeriesRecordings#THE_BEGINNING} and seasonNumber == startFromSeason then
544     * only record episodes with a episodeNumber >= this
545     */
546    public int getStartFromEpisode() {
547        return mStartFromEpisode;
548    }
549
550    /**
551     * If not == {@link SeriesRecordings#THE_BEGINNING} then only record episodes with a
552     * seasonNumber >= this
553     */
554    public int getStartFromSeason() {
555        return mStartFromSeason;
556    }
557
558    /**
559     * Returns the channel recording option.
560     */
561    @ChannelOption public int getChannelOption() {
562        return mChannelOption;
563    }
564
565    /**
566     * Returns the canonical genre ID's.
567     */
568    public int[] getCanonicalGenreIds() {
569        return mCanonicalGenreIds;
570    }
571
572    /**
573     * Returns the poster URI.
574     */
575    public String getPosterUri() {
576        return mPosterUri;
577    }
578
579    /**
580     * Returns the photo URI.
581     */
582    public String getPhotoUri() {
583        return mPhotoUri;
584    }
585
586    /**
587     * Returns the state of series recording.
588     */
589    @SeriesState public int getState() {
590        return mState;
591    }
592
593    /**
594     * Checks whether the series recording is stopped or not.
595     */
596    public boolean isStopped() {
597        return mState == STATE_SERIES_STOPPED;
598    }
599
600    @Override
601    public boolean equals(Object o) {
602        if (this == o) return true;
603        if (!(o instanceof SeriesRecording)) return false;
604        SeriesRecording that = (SeriesRecording) o;
605        return mPriority == that.mPriority
606                && mChannelId == that.mChannelId
607                && mStartFromSeason == that.mStartFromSeason
608                && mStartFromEpisode == that.mStartFromEpisode
609                && Objects.equals(mId, that.mId)
610                && Objects.equals(mTitle, that.mTitle)
611                && Objects.equals(mDescription, that.mDescription)
612                && Objects.equals(mLongDescription, that.mLongDescription)
613                && Objects.equals(mSeriesId, that.mSeriesId)
614                && mChannelOption == that.mChannelOption
615                && Arrays.equals(mCanonicalGenreIds, that.mCanonicalGenreIds)
616                && Objects.equals(mPosterUri, that.mPosterUri)
617                && Objects.equals(mPhotoUri, that.mPhotoUri)
618                && mState == that.mState;
619    }
620
621    @Override
622    public int hashCode() {
623        return Objects.hash(mPriority, mChannelId, mStartFromSeason, mStartFromEpisode, mId,
624                mTitle, mDescription, mLongDescription, mSeriesId, mChannelOption,
625                mCanonicalGenreIds, mPosterUri, mPhotoUri, mState);
626    }
627
628    @Override
629    public String toString() {
630        return "SeriesRecording{" +
631                "inputId=" + mInputId +
632                ", channelId=" + mChannelId +
633                ", id='" + mId + '\'' +
634                ", priority=" + mPriority +
635                ", title='" + mTitle + '\'' +
636                ", description='" + mDescription + '\'' +
637                ", longDescription='" + mLongDescription + '\'' +
638                ", startFromSeason=" + mStartFromSeason +
639                ", startFromEpisode=" + mStartFromEpisode +
640                ", channelOption=" + mChannelOption +
641                ", canonicalGenreIds=" + Arrays.toString(mCanonicalGenreIds) +
642                ", posterUri=" + mPosterUri +
643                ", photoUri=" + mPhotoUri +
644                ", state=" + mState +
645                '}';
646    }
647
648    private SeriesRecording(long id, long priority, String title, String description,
649            String longDescription, String inputId, long channelId, String seriesId,
650            int startFromSeason, int startFromEpisode, int channelOption, int[] canonicalGenreIds,
651            String posterUri, String photoUri, int state) {
652        this.mId = id;
653        this.mPriority = priority;
654        this.mTitle = title;
655        this.mDescription = description;
656        this.mLongDescription = longDescription;
657        this.mInputId = inputId;
658        this.mChannelId = channelId;
659        this.mSeriesId = seriesId;
660        this.mStartFromSeason = startFromSeason;
661        this.mStartFromEpisode = startFromEpisode;
662        this.mChannelOption = channelOption;
663        this.mCanonicalGenreIds = canonicalGenreIds;
664        this.mPosterUri = posterUri;
665        this.mPhotoUri = photoUri;
666        this.mState = state;
667    }
668
669    @Override
670    public int describeContents() {
671        return 0;
672    }
673
674    @Override
675    public void writeToParcel(Parcel out, int paramInt) {
676        out.writeLong(mId);
677        out.writeLong(mPriority);
678        out.writeString(mTitle);
679        out.writeString(mDescription);
680        out.writeString(mLongDescription);
681        out.writeString(mInputId);
682        out.writeLong(mChannelId);
683        out.writeString(mSeriesId);
684        out.writeInt(mStartFromSeason);
685        out.writeInt(mStartFromEpisode);
686        out.writeInt(mChannelOption);
687        out.writeIntArray(mCanonicalGenreIds);
688        out.writeString(mPosterUri);
689        out.writeString(mPhotoUri);
690        out.writeInt(mState);
691    }
692
693    /**
694     * Returns an array containing all of the elements in the list.
695     */
696    public static SeriesRecording[] toArray(Collection<SeriesRecording> series) {
697        return series.toArray(new SeriesRecording[series.size()]);
698    }
699
700    /**
701     * Returns {@code true} if the {@code program} is part of the series and meets the season and
702     * episode constraints.
703     */
704    public boolean matchProgram(Program program) {
705        return matchProgram(program, mChannelOption);
706    }
707
708    /**
709     * Returns {@code true} if the {@code program} is part of the series and meets the season and
710     * episode constraints. It checks the channel option only if {@code checkChannelOption} is
711     * {@code true}.
712     */
713    public boolean matchProgram(Program program, @ChannelOption int channelOption) {
714        String seriesId = program.getSeriesId();
715        long channelId = program.getChannelId();
716        String seasonNumber = program.getSeasonNumber();
717        String episodeNumber = program.getEpisodeNumber();
718        if (!mSeriesId.equals(seriesId) || (channelOption == SeriesRecording.OPTION_CHANNEL_ONE
719                && mChannelId != channelId)) {
720            return false;
721        }
722        // Season number and episode number matches if
723        // start_season_number < program_season_number
724        // || (start_season_number == program_season_number
725        // && start_episode_number <= program_episode_number).
726        if (mStartFromSeason == SeriesRecordings.THE_BEGINNING
727                || TextUtils.isEmpty(seasonNumber)) {
728            return true;
729        } else {
730            int intSeasonNumber;
731            try {
732                intSeasonNumber = Integer.valueOf(seasonNumber);
733            } catch (NumberFormatException e) {
734                return true;
735            }
736            if (intSeasonNumber > mStartFromSeason) {
737                return true;
738            } else if (intSeasonNumber < mStartFromSeason) {
739                return false;
740            }
741        }
742        if (mStartFromEpisode == SeriesRecordings.THE_BEGINNING
743                || TextUtils.isEmpty(episodeNumber)) {
744            return true;
745        } else {
746            int intEpisodeNumber;
747            try {
748                intEpisodeNumber = Integer.valueOf(episodeNumber);
749            } catch (NumberFormatException e) {
750                return true;
751            }
752            return intEpisodeNumber >= mStartFromEpisode;
753        }
754    }
755}
756