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