content: package allen.town.podcast.playback.cast;
  
  import android.content.ContentResolver;
  import android.util.Log;
  import android.text.TextUtils;
  import com.google.android.gms.cast.CastDevice;
  import com.google.android.gms.cast.MediaInfo;
  import com.google.android.gms.cast.MediaMetadata;
  import com.google.android.gms.cast.framework.CastSession;
  import com.google.android.gms.common.images.WebImage;
  import allen.town.podcast.model.feed.Feed;
  import allen.town.podcast.model.feed.FeedItem;
  import allen.town.podcast.model.feed.FeedMedia;
  import allen.town.podcast.model.playback.Playable;
  import allen.town.podcast.model.playback.RemoteMedia;
  
  import java.util.List;
  
  /**
   * Helper functions for Cast support.
   */
  public class CastUtils {
      private CastUtils() {
      }
  
      private static final String TAG = "CastUtils";
  
      public static final String KEY_MEDIA_ID = "allen.town.podcast.core.cast.MediaId";
  
      public static final String KEY_EPISODE_IDENTIFIER = "allen.town.podcast.core.cast.EpisodeId";
      public static final String KEY_EPISODE_LINK = "allen.town.podcast.core.cast.EpisodeLink";
      public static final String KEY_STREAM_URL = "allen.town.podcast.core.cast.StreamUrl";
      public static final String KEY_FEED_URL = "allen.town.podcast.core.cast.FeedUrl";
      public static final String KEY_FEED_WEBSITE = "allen.town.podcast.core.cast.FeedWebsite";
      public static final String KEY_EPISODE_NOTES = "allen.town.podcast.core.cast.EpisodeNotes";
  
      /**
       * The field <code>FormatVersion</code> specifies which version of MediaMetaData
       * fields we're using. Future implementations should try to be backwards compatible with earlier
       * versions, and earlier versions should be forward compatible until the version indicated by
       * <code>MAX_VERSION_FORWARD_COMPATIBILITY</code>. If an update makes the format unreadable for
       * an earlier version, then its version number should be greater than the
       * <code>MAX_VERSION_FORWARD_COMPATIBILITY</code> value set on the earlier one, so that it
       * doesn't try to parse the object.
       */
      public static final String KEY_FORMAT_VERSION = "allen.town.podcast.core.cast.FormatVersion";
      public static final int FORMAT_VERSION_VALUE = 1;
      public static final int MAX_VERSION_FORWARD_COMPATIBILITY = 9999;
  
      public static boolean isCastable(Playable media, CastSession castSession) {
          if (media == null || castSession == null || castSession.getCastDevice() == null) {
              return false;
          }
          if (media instanceof FeedMedia || media instanceof RemoteMedia) {
              String url = media.getStreamUrl();
              if (url == null || url.isEmpty()) {
                  return false;
              }
              if (url.startsWith(ContentResolver.SCHEME_CONTENT)) {
                  return false; // Local feed
              }
              switch (media.getMediaType()) {
                  case AUDIO:
                      return castSession.getCastDevice().hasCapability(CastDevice.CAPABILITY_AUDIO_OUT);
                  case VIDEO:
                      return castSession.getCastDevice().hasCapability(CastDevice.CAPABILITY_VIDEO_OUT);
                  default:
                      return false;
              }
          }
          return false;
      }
  
      /**
       * Converts {@link MediaInfo} objects into the appropriate implementation of {@link Playable}.
       * @return {@link Playable} object in a format proper for casting.
       */
      public static Playable makeRemoteMedia(MediaInfo media) {
          MediaMetadata metadata = media.getMetadata();
          int version = metadata.getInt(KEY_FORMAT_VERSION);
          if (version <= 0 || version > MAX_VERSION_FORWARD_COMPATIBILITY) {
              Log.w(TAG, "MediaInfo object obtained from the cast device is not compatible with this"
                      + "version of CastUtils, curVer=" + FORMAT_VERSION_VALUE
                      + ", object version=" + version);
              return null;
          }
          List<WebImage> imageList = metadata.getImages();
          String imageUrl = null;
          if (!imageList.isEmpty()) {
              imageUrl = imageList.get(0).getUrl().toString();
          }
          String notes = metadata.getString(KEY_EPISODE_NOTES);
          RemoteMedia result = new RemoteMedia(media.getContentId(),
                  metadata.getString(KEY_EPISODE_IDENTIFIER),
                  metadata.getString(KEY_FEED_URL),
                  metadata.getString(MediaMetadata.KEY_SUBTITLE),
                  metadata.getString(MediaMetadata.KEY_TITLE),
                  metadata.getString(KEY_EPISODE_LINK),
                  metadata.getString(MediaMetadata.KEY_ARTIST),
                  imageUrl,
                  metadata.getString(KEY_FEED_WEBSITE),
                  media.getContentType(),
                  metadata.getDate(MediaMetadata.KEY_RELEASE_DATE).getTime(),
                  notes);
          if (result.getDuration() == 0 && media.getStreamDuration() > 0) {
              result.setDuration((int) media.getStreamDuration());
          }
          return result;
      }
  
      /**
       * Compares a {@link MediaInfo} instance with a {@link FeedMedia} one and evaluates whether they
       * represent the same podcast episode.
       *
       * @param info      the {@link MediaInfo} object to be compared.
       * @param media     the {@link FeedMedia} object to be compared.
       * @return <true>true</true> if there's a match, <code>false</code> otherwise.
       *
       * @see RemoteMedia#equals(Object)
       */
      public static boolean matches(MediaInfo info, FeedMedia media) {
          if (info == null || media == null) {
              return false;
          }
          if (!TextUtils.equals(info.getContentId(), media.getStreamUrl())) {
              return false;
          }
          MediaMetadata metadata = info.getMetadata();
          FeedItem fi = media.getItem();
          if (fi == null || metadata == null
                  || !TextUtils.equals(metadata.getString(KEY_EPISODE_IDENTIFIER), fi.getItemIdentifier())) {
              return false;
          }
          Feed feed = fi.getFeed();
          return feed != null && TextUtils.equals(metadata.getString(KEY_FEED_URL), feed.getDownload_url());
      }
  
      /**
       * Compares a {@link MediaInfo} instance with a {@link RemoteMedia} one and evaluates whether they
       * represent the same podcast episode.
       *
       * @param info      the {@link MediaInfo} object to be compared.
       * @param media     the {@link RemoteMedia} object to be compared.
       * @return <true>true</true> if there's a match, <code>false</code> otherwise.
       *
       * @see RemoteMedia#equals(Object)
       */
      public static boolean matches(MediaInfo info, RemoteMedia media) {
          if (info == null || media == null) {
              return false;
          }
          if (!TextUtils.equals(info.getContentId(), media.getStreamUrl())) {
              return false;
          }
          MediaMetadata metadata = info.getMetadata();
          return metadata != null
                  && TextUtils.equals(metadata.getString(KEY_EPISODE_IDENTIFIER), media.getEpisodeIdentifier())
                  && TextUtils.equals(metadata.getString(KEY_FEED_URL), media.getFeedUrl());
      }
  
      /**
       * Compares a {@link MediaInfo} instance with a {@link Playable} and evaluates whether they
       * represent the same podcast episode. Useful every time we get a MediaInfo from the Cast Device
       * and want to avoid unnecessary conversions.
       *
       * @param info      the {@link MediaInfo} object to be compared.
       * @param media     the {@link Playable} object to be compared.
       * @return <true>true</true> if there's a match, <code>false</code> otherwise.
       *
       * @see RemoteMedia#equals(Object)
       */
      public static boolean matches(MediaInfo info, Playable media) {
          if (info == null || media == null) {
              return false;
          }
          if (media instanceof RemoteMedia) {
              return matches(info, (RemoteMedia) media);
          }
          return media instanceof FeedMedia && matches(info, (FeedMedia) media);
      }
  }
  