Example #1
0
 @Override
 public Uri getCanonicalUri() {
   return ProviderUtils.toContentUri(
       MediaProvider.AUTHORITY,
       Cast.PATH
           + "/"
           + getLong(getColumnIndex(CAST))
           + "/"
           + PATH
           + "/"
           + getLong(getColumnIndex(_ID)));
 }
Example #2
0
@UriPath(CastMedia.PATH)
public class CastMedia extends JsonSyncableItem {
  private static final String TAG = CastMedia.class.getSimpleName();

  @DBColumn(type = TextColumn.class)
  public static final String _AUTHOR = "author";

  @DBColumn(type = TextColumn.class)
  public static final String _AUTHOR_URI = "author_uri";

  @DBColumn(type = TextColumn.class)
  public static final String _TITLE = "title";

  @DBColumn(type = TextColumn.class)
  public static final String _DESCRIPTION = "description";

  @DBColumn(type = TextColumn.class)
  public static final String _LANGUAGE = "language";

  @DBColumn(type = TextColumn.class)
  public static final String _MEDIA_URL = "url"; // the body of the object

  @DBColumn(type = TextColumn.class)
  public static final String _LOW_BITRATE_URL = "compressed"; // the more compressed body of the
  // object
  @DBColumn(type = TextColumn.class)
  public static final String _LOW_BITRATE_MIME_TYPE = "compressed_mime"; // type of the media

  @DBColumn(type = TextColumn.class)
  public static final String _LOCAL_URI = "local_uri"; // any local copy of the main media

  @DBColumn(type = TextColumn.class)
  public static final String _MIME_TYPE = "mimetype"; // type of the media

  @DBColumn(type = IntegerColumn.class)
  public static final String _DURATION = "duration";

  @DBColumn(type = TextColumn.class)
  public static final String _THUMBNAIL = "thumbnail";

  @DBColumn(type = BooleanColumn.class)
  public static final String _KEEP_OFFLINE = "offline";

  @DBColumn(type = TextColumn.class)
  public static final String _THUMB_LOCAL = "local_thumb"; // filename of the local thumbnail

  @DBForeignKeyColumn(parent = Cast.class)
  public static final String CAST = "cast_id";

  public static final String PATH = "media";
  public static final String CASTS_CASTMEDIA_PATH =
      Cast.PATH + "/" + ForeignKeyDBHelper.WILDCARD_PATH_SEGMENT + "/" + PATH;
  public static final String SERVER_PATH = "media/";

  public static final String[] PROJECTION = {
    _ID,
    _PUBLIC_URI,
    _MODIFIED_DATE,
    _CREATED_DATE,
    _AUTHOR,
    _AUTHOR_URI,
    _TITLE,
    _DESCRIPTION,
    _LANGUAGE,
    _MEDIA_URL,
    _LOCAL_URI,
    _MIME_TYPE,
    _LOW_BITRATE_URL,
    _LOW_BITRATE_MIME_TYPE,
    _DURATION,
    _THUMBNAIL,
    _THUMB_LOCAL,
    _KEEP_OFFLINE
  };

  public static final String MIMETYPE_HTML = "text/html",
      MIMETYPE_3GPP = "video/3gpp",
      MIMETYPE_MPEG4 = "video/mpeg4";

  public static final Uri CONTENT_URI =
      ProviderUtils.toContentUri(MediaProvider.AUTHORITY, CASTS_CASTMEDIA_PATH);

  public CastMedia(Cursor c) {
    super(c);
  }

  @Override
  public Uri getCanonicalUri() {
    return ProviderUtils.toContentUri(
        MediaProvider.AUTHORITY,
        Cast.PATH
            + "/"
            + getLong(getColumnIndex(CAST))
            + "/"
            + PATH
            + "/"
            + getLong(getColumnIndex(_ID)));
  }

  @Override
  public Uri getContentUri() {
    return CONTENT_URI;
  }

  @Override
  public SyncMap getSyncMap() {

    return SYNC_MAP;
  }

  public static Uri getCast(Uri castMediaUri) {
    return ProviderUtils.removeLastPathSegments(castMediaUri, 2);
  }

  /**
   * Returns the uri for the media. It will return, in order, the local uri if it's present, the
   * low-res uri (if {@link #shouldShowLowQuality(Context)} says so), or the full-res URI.
   *
   * @param context
   * @param c
   * @return null or a url pointing to the media
   */
  public static Uri getMedia(Context context, Cursor c) {
    Uri media;

    final int mediaLocalCol = c.getColumnIndexOrThrow(_LOCAL_URI);
    final int mediaCol = c.getColumnIndexOrThrow(_MEDIA_URL);

    if (!c.isNull(mediaLocalCol)) {
      media = Uri.parse(c.getString(mediaLocalCol));

    } else if (shouldShowLowQuality(context)) {
      final int mediaLowResCol = c.getColumnIndexOrThrow(_LOW_BITRATE_URL);
      media = Uri.parse(c.getString(mediaLowResCol));

    } else if (!c.isNull(mediaCol)) {
      media = Uri.parse(c.getString(mediaCol));

    } else {
      media = null;
    }

    return media;
  }

  /**
   * Attempts to guess if the video player should show a high quality version of the video or a
   * lower bitrate version.
   *
   * @return true if it seems as though playing high-quality would be expensive or wouldn't work
   */
  public static boolean shouldShowLowQuality(Context context) {
    final ConnectivityManager cm =
        (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    final boolean metered = ConnectivityManagerCompat.isActiveNetworkMetered(cm);
    final NetworkInfo net = cm.getActiveNetworkInfo();

    if (metered || net == null || net.isRoaming()) {
      return true;
    }

    // this is because these devices tend to not be able to be powerful enough to show the full
    // res video
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
      return true;
    }

    final int type = net.getType();

    switch (type) {
        // generally these are fast and cheap/free
      case ConnectivityManager.TYPE_WIFI:
      case ConnectivityManager.TYPE_ETHERNET:
      case ConnectivityManager.TYPE_WIMAX:
        return false;

      default:
        return true;
    }
  }

  /**
   * @param c
   * @return true if there is a low-res copy of the media
   */
  public static boolean hasLowBitrate(Cursor c) {
    final String mediaString = c.getString(c.getColumnIndex(CastMedia._LOW_BITRATE_URL));

    return mediaString != null && mediaString.length() > 0;
  }

  /**
   * @param context
   * @param c
   * @param castMediaUri
   */
  public static void showMedia(Context context, Cursor c, Uri castMediaUri) {
    final String mediaString = c.getString(c.getColumnIndex(CastMedia._MEDIA_URL));
    final String locMediaString = c.getString(c.getColumnIndex(CastMedia._LOCAL_URI));
    String mimeType = null;

    Uri media;

    if (locMediaString != null) {
      media = Uri.parse(locMediaString);
      if ("file".equals(media.getScheme())) {
        mimeType = c.getString(c.getColumnIndex(CastMedia._MIME_TYPE));
      }

    } else if (mediaString != null) {
      media = Uri.parse(mediaString);
      mimeType = c.getString(c.getColumnIndex(CastMedia._MIME_TYPE));

      // we strip this because we don't really want to force them to go to the browser.
      if ("text/html".equals(mimeType)) {
        mimeType = null;
      }
    } else {
      Log.e(TAG, "asked to show media for " + castMediaUri + " but there was nothing to show");
      return;
    }

    final Intent i = new Intent(Intent.ACTION_VIEW);
    i.setDataAndType(media, mimeType);

    if (mimeType != null && mimeType.startsWith("video/")) {
      context.startActivity(
          new Intent(
              Intent.ACTION_VIEW,
              ContentUris.withAppendedId(
                  castMediaUri, c.getLong(c.getColumnIndex(CastMedia._ID)))));
    } else {
      // setting the MIME type for URLs doesn't work.
      try {
        context.startActivity(i);
      } catch (final ActivityNotFoundException e) {
        // try it again, but without a mime type.
        if (mimeType != null) {
          i.setDataAndType(media, null);
        }
        try {
          context.startActivity(i);
        } catch (final ActivityNotFoundException e2) {
          Toast.makeText(context, R.string.error_cast_media_no_activities, Toast.LENGTH_LONG)
              .show();
        }
      }
    }
  }

  public static Uri getThumbnail(Cursor c, int thumbCol, int thumbLocalCol) {
    Uri thumbnail;
    if (!c.isNull(thumbLocalCol)) {
      thumbnail = Uri.parse(c.getString(thumbLocalCol));
    } else if (!c.isNull(thumbCol)) {
      thumbnail = Uri.parse(c.getString(thumbCol));
    } else {
      thumbnail = null;
    }
    return thumbnail;
  }

  /**
   * Guesses the mime type from the URL
   *
   * @param url
   * @return the inferred mime type based on the file extension or null if it can't determine one
   */
  public static String guessMimeTypeFromUrl(String url) {

    // this was improved in Gingerbread
    // http://code.google.com/p/android/issues/detail?id=10100
    // so we have some pre-defined types here so we can make sure to return SOMETHING.
    String mimeType = URLConnection.guessContentTypeFromName(url);
    if (mimeType != null) {
      return mimeType;
    }

    if (url.endsWith(".jpg") || url.endsWith(".jpeg")) {
      mimeType = "image/jpeg";

    } else if (url.endsWith(".3gp")) {
      mimeType = "video/3gpp";

    } else if (url.endsWith(".mp4") || url.endsWith(".mpeg4")) {
      mimeType = "video/mp4";

    } else if (url.endsWith(".png")) {
      mimeType = "image/png";
    }

    return mimeType;
  }

  public static final ItemSyncMap SYNC_MAP = new ItemSyncMap();

  public static class ItemSyncMap extends JsonSyncableItem.ItemSyncMap {
    /** */
    private static final long serialVersionUID = 8477549708016150941L;

    public static final String KEY_COMPRESSED = "compressed_file";

    public static final String KEY_PRIMARY = "primary";

    public ItemSyncMap() {
      super();

      this.addFlag(FLAG_PARENT_MUST_SYNC_FIRST);

      put(_TITLE, new SyncFieldMap("title", SyncFieldMap.STRING, SyncFieldMap.FLAG_OPTIONAL));
      put(
          _DESCRIPTION,
          new SyncFieldMap("description", SyncFieldMap.STRING, SyncFieldMap.FLAG_OPTIONAL));
      put(_LANGUAGE, new SyncFieldMap("language", SyncFieldMap.STRING));
      put(
          _AUTHOR,
          new SyncChildField(
              _AUTHOR, "author", "display_name", SyncFieldMap.STRING, SyncFieldMap.FLAG_OPTIONAL));
      put(
          _AUTHOR_URI,
          new SyncChildField(
              _AUTHOR_URI, "author", "uri", SyncFieldMap.STRING, SyncFieldMap.FLAG_OPTIONAL));

      put(
          "_resources",
          new SyncCustom("resources", SyncCustom.SYNC_FROM | SyncCustom.FLAG_OPTIONAL) {

            @Override
            public Object toJSON(Context context, Uri localItem, Cursor c, String lProp)
                throws JSONException, NetworkProtocolException, IOException {
              return null;
            }

            @Override
            public ContentValues fromJSON(
                Context context, Uri localItem, JSONObject item, String lProp)
                throws JSONException, NetworkProtocolException, IOException {
              final ContentValues cv = new ContentValues();

              final JSONObject jo = item.getJSONObject(remoteKey);
              if (jo.has(KEY_COMPRESSED)) {
                final JSONObject primary = jo.getJSONObject(KEY_COMPRESSED);
                cv.put(_LOW_BITRATE_MIME_TYPE, primary.getString("mime_type"));
                cv.put(_LOW_BITRATE_URL, primary.getString("url"));
              }

              if (jo.has(KEY_PRIMARY)) {
                final JSONObject primary = jo.getJSONObject(KEY_PRIMARY);
                cv.put(_MIME_TYPE, primary.getString("mime_type"));
                cv.put(_MEDIA_URL, primary.getString("url"));
              }

              if (jo.has("medium")) {
                final JSONObject screenshot = jo.getJSONObject("medium");
                cv.put(_THUMBNAIL, screenshot.getString("url"));

              } else if (jo.has("screenshot")) {
                final JSONObject screenshot = jo.getJSONObject("screenshot");
                cv.put(_THUMBNAIL, screenshot.getString("url"));
              }

              return cv;
            }
          });

      // no MIME type is passed with a link media type, so we need to add one in.
      put(
          "_content_type",
          new SyncCustom("content_type", SyncItem.SYNC_BOTH) {

            @Override
            public Object toJSON(Context context, Uri localItem, Cursor c, String lProp)
                throws JSONException, NetworkProtocolException, IOException {
              String mimeType = c.getString(c.getColumnIndex(_MIME_TYPE));
              final String localUri = c.getString(c.getColumnIndex(_LOCAL_URI));

              if (mimeType == null && localUri != null) {

                mimeType = guessMimeTypeFromUrl(localUri);
                if (mimeType != null) {
                  Log.d(TAG, "guessed MIME type from uri: " + localUri + ": " + mimeType);
                }
              }

              if (mimeType == null) {
                return null;
              }

              if (mimeType.startsWith("video/")) {
                return "videomedia";
              } else if (mimeType.startsWith("image/")) {
                return "imagemedia";
              } else {
                return null;
              }
            }

            @Override
            public ContentValues fromJSON(
                Context context, Uri localItem, JSONObject item, String lProp)
                throws JSONException, NetworkProtocolException, IOException {
              final ContentValues cv = new ContentValues();
              final String content_type = item.getString(remoteKey);

              if ("linkedmedia".equals(content_type)) {
                cv.put(_MIME_TYPE, MIMETYPE_HTML);
              }

              return cv;
            }
          });

      // the media URL can come from either the flattened "url" attribute or the expanded
      // "resources" structure above.
      put(
          _MEDIA_URL,
          new SyncFieldMap(
              "url", SyncFieldMap.STRING, SyncFieldMap.SYNC_FROM | SyncItem.FLAG_OPTIONAL));
      put(_MIME_TYPE, new SyncFieldMap("mime_type", SyncFieldMap.STRING, SyncItem.FLAG_OPTIONAL));
      put(
          _DURATION,
          new SyncFieldMap(
              "duration", SyncFieldMap.DURATION, SyncFieldMap.SYNC_FROM | SyncItem.FLAG_OPTIONAL));

      put(
          _THUMBNAIL,
          new SyncFieldMap(
              "preview_image",
              SyncFieldMap.STRING,
              SyncFieldMap.SYNC_FROM | SyncItem.FLAG_OPTIONAL));
    }

    @Override
    public void onPostSyncItem(
        Context context, Account account, Uri uri, JSONObject item, boolean updated)
        throws SyncException, IOException {
      super.onPostSyncItem(context, account, uri, item, updated);
      if (uri != null) {
        Log.d(TAG, "Starting media sync for " + uri);
        context.startService(new Intent(MediaSync.ACTION_SYNC_RESOURCES, uri));
      }
    }
  }
}