edu.mit.mobile.android.locast.data.CastMedia.java Source code

Java tutorial

Introduction

Here is the source code for edu.mit.mobile.android.locast.data.CastMedia.java

Source

package edu.mit.mobile.android.locast.data;
/*
 * Copyright (C) 2011  MIT Mobile Experience Lab
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

import java.io.IOException;
import java.net.URLConnection;

import org.json.JSONException;
import org.json.JSONObject;

import android.accounts.Account;
import android.content.ActivityNotFoundException;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.support.v4.net.ConnectivityManagerCompat;
import android.util.Log;
import android.widget.Toast;
import edu.mit.mobile.android.content.ForeignKeyDBHelper;
import edu.mit.mobile.android.content.ProviderUtils;
import edu.mit.mobile.android.content.UriPath;
import edu.mit.mobile.android.content.column.BooleanColumn;
import edu.mit.mobile.android.content.column.DBColumn;
import edu.mit.mobile.android.content.column.DBForeignKeyColumn;
import edu.mit.mobile.android.content.column.IntegerColumn;
import edu.mit.mobile.android.content.column.TextColumn;
import edu.mit.mobile.android.locast.memorytraces.R;
import edu.mit.mobile.android.locast.net.NetworkProtocolException;
import edu.mit.mobile.android.locast.sync.MediaSync;

@UriPath(CastMedia.PATH)
public class CastMedia extends JsonSyncableItem {
    private static final String TAG = CastMedia.class.getSimpleName();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    public final static 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 final static 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));
            }
        }
    }
}