Java tutorial
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)); } } } }