Java tutorial
/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.dragonTV.data; import android.app.SearchManager; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.media.Rating; import android.provider.BaseColumns; import android.util.Log; import com.example.android.dragonTV.R; import com.example.android.dragonTV.model.Movie; import org.json.JSONException; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Contains logic to return specific words from the video database, and * load the video database table when it needs to be created. */ public class VideoDatabase { //The columns we'll include in the video database table public static final String KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1; public static final String KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2; public static final String KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE; public static final String KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE; public static final String KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE; public static final String KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH; public static final String KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT; public static final String KEY_AUDIO_CHANNEL_CONFIG = SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG; public static final String KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE; public static final String KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE; public static final String KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE; public static final String KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE; public static final String KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR; public static final String KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION; public static final String KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION; private static final String TAG = "VideoDatabase"; private static final String DATABASE_NAME = "video_database_leanback"; private static final String FTS_VIRTUAL_TABLE = "Leanback_table"; private static final int DATABASE_VERSION = 2; private static final HashMap<String, String> COLUMN_MAP = buildColumnMap(); private static int CARD_WIDTH = 313; private static int CARD_HEIGHT = 176; private final VideoDatabaseOpenHelper mDatabaseOpenHelper; /** * Constructor * * @param context The Context within which to work, used to create the DB */ public VideoDatabase(Context context) { mDatabaseOpenHelper = new VideoDatabaseOpenHelper(context); } /** * Builds a map for all columns that may be requested, which will be given to the * SQLiteQueryBuilder. This is a good way to define aliases for column names, but must include * all columns, even if the value is the key. This allows the ContentProvider to request * columns w/o the need to know real column names and create the alias itself. */ private static HashMap<String, String> buildColumnMap() { HashMap<String, String> map = new HashMap<String, String>(); map.put(KEY_NAME, KEY_NAME); map.put(KEY_DESCRIPTION, KEY_DESCRIPTION); map.put(KEY_ICON, KEY_ICON); map.put(KEY_DATA_TYPE, KEY_DATA_TYPE); map.put(KEY_IS_LIVE, KEY_IS_LIVE); map.put(KEY_VIDEO_WIDTH, KEY_VIDEO_WIDTH); map.put(KEY_VIDEO_HEIGHT, KEY_VIDEO_HEIGHT); map.put(KEY_AUDIO_CHANNEL_CONFIG, KEY_AUDIO_CHANNEL_CONFIG); map.put(KEY_PURCHASE_PRICE, KEY_PURCHASE_PRICE); map.put(KEY_RENTAL_PRICE, KEY_RENTAL_PRICE); map.put(KEY_RATING_STYLE, KEY_RATING_STYLE); map.put(KEY_RATING_SCORE, KEY_RATING_SCORE); map.put(KEY_PRODUCTION_YEAR, KEY_PRODUCTION_YEAR); map.put(KEY_COLUMN_DURATION, KEY_COLUMN_DURATION); map.put(KEY_ACTION, KEY_ACTION); map.put(BaseColumns._ID, "rowid AS " + BaseColumns._ID); map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID); return map; } /** * Returns a Cursor positioned at the word specified by rowId * * @param rowId id of word to retrieve * @param columns The columns to include, if null then all are included * @return Cursor positioned to matching word, or null if not found. */ public Cursor getWord(String rowId, String[] columns) { /* This builds a query that looks like: * SELECT <columns> FROM <table> WHERE rowid = <rowId> */ String selection = "rowid = ?"; String[] selectionArgs = new String[] { rowId }; return query(selection, selectionArgs, columns); } /** * Returns a Cursor over all words that match the first letter of the given query * * @param query The string to search for * @param columns The columns to include, if null then all are included * @return Cursor over all words that match, or null if none found. */ public Cursor getWordMatch(String query, String[] columns) { /* This builds a query that looks like: * SELECT <columns> FROM <table> WHERE <KEY_WORD> MATCH 'query*' * which is an FTS3 search for the query text (plus a wildcard) inside the word column. * * - "rowid" is the unique id for all rows but we need this value for the "_id" column in * order for the Adapters to work, so the columns need to make "_id" an alias for "rowid" * - "rowid" also needs to be used by the SUGGEST_COLUMN_INTENT_DATA alias in order * for suggestions to carry the proper intent data.SearchManager * These aliases are defined in the VideoProvider when queries are made. * - This can be revised to also search the definition text with FTS3 by changing * the selection clause to use FTS_VIRTUAL_TABLE instead of KEY_WORD (to search across * the entire table, but sorting the relevance could be difficult. */ String selection = KEY_NAME + " MATCH ?"; String[] selectionArgs = new String[] { query + "*" }; return query(selection, selectionArgs, columns); } /** * Performs a database query. * * @param selection The selection clause * @param selectionArgs Selection arguments for "?" components in the selection * @param columns The columns to return * @return A Cursor over all rows matching the query */ private Cursor query(String selection, String[] selectionArgs, String[] columns) { /* The SQLiteBuilder provides a map for all possible columns requested to * actual columns in the database, creating a simple column alias mechanism * by which the ContentProvider does not need to know the real column names */ SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); builder.setTables(FTS_VIRTUAL_TABLE); builder.setProjectionMap(COLUMN_MAP); Cursor cursor = new PaginatedCursor(builder.query(mDatabaseOpenHelper.getReadableDatabase(), columns, selection, selectionArgs, null, null, null)); if (cursor == null) { return null; } else if (!cursor.moveToFirst()) { cursor.close(); return null; } return cursor; } /** * This creates/opens the database. */ private static class VideoDatabaseOpenHelper extends SQLiteOpenHelper { private final Context mHelperContext; private SQLiteDatabase mDatabase; VideoDatabaseOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); mHelperContext = context; } /* Note that FTS3 does not support column constraints and thus, you cannot * declare a primary key. However, "rowid" is automatically used as a unique * identifier, so when making requests, we will use "_id" as an alias for "rowid" */ private static final String FTS_TABLE_CREATE = "CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE + " USING fts3 (" + KEY_NAME + ", " + KEY_DESCRIPTION + "," + KEY_ICON + "," + KEY_DATA_TYPE + "," + KEY_IS_LIVE + "," + KEY_VIDEO_WIDTH + "," + KEY_VIDEO_HEIGHT + "," + KEY_AUDIO_CHANNEL_CONFIG + "," + KEY_PURCHASE_PRICE + "," + KEY_RENTAL_PRICE + "," + KEY_RATING_STYLE + "," + KEY_RATING_SCORE + "," + KEY_PRODUCTION_YEAR + "," + KEY_COLUMN_DURATION + "," + KEY_ACTION + ");"; @Override public void onCreate(SQLiteDatabase db) { mDatabase = db; mDatabase.execSQL(FTS_TABLE_CREATE); loadDatabase(); } /** * Starts a thread to load the database table with words */ private void loadDatabase() { new Thread(new Runnable() { public void run() { try { loadMovies(); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); } private void loadMovies() throws IOException { Log.d(TAG, "Loading movies..."); HashMap<String, List<Movie>> movies = null; try { VideoProvider.setContext(mHelperContext); movies = VideoProvider.buildMedia(mHelperContext, mHelperContext.getResources().getString(R.string.catalog_url)); } catch (JSONException e) { Log.e(TAG, "JSon Exception when loading movie", e); } for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) { List<Movie> list = entry.getValue(); for (Movie movie : list) { long id = addMovie(movie); if (id < 0) { Log.e(TAG, "unable to add movie: " + movie.toString()); } } } // add dummy movies to illustrate action deep link in search detail // Android TV Search requires that the medias title, MIME type, production year, // and duration all match exactly to those found from Googles servers. addMovieForDeepLink(mHelperContext.getString(R.string.noah_title), mHelperContext.getString(R.string.noah_description), R.drawable.noah, 8280000, "2014"); addMovieForDeepLink(mHelperContext.getString(R.string.dragon2_title), mHelperContext.getString(R.string.dragon2_description), R.drawable.dragon2, 6300000, "2014"); addMovieForDeepLink(mHelperContext.getString(R.string.maleficent_title), mHelperContext.getString(R.string.maleficent_description), R.drawable.maleficent, 5820000, "2014"); } /** * Add a movie to the database. * * @return rowId or -1 if failed */ public long addMovie(Movie movie) { ContentValues initialValues = new ContentValues(); initialValues.put(KEY_NAME, movie.getTitle()); initialValues.put(KEY_DESCRIPTION, movie.getDescription()); initialValues.put(KEY_ICON, movie.getCardImageUrl()); initialValues.put(KEY_DATA_TYPE, "video/mp4"); initialValues.put(KEY_IS_LIVE, false); initialValues.put(KEY_VIDEO_WIDTH, CARD_WIDTH); initialValues.put(KEY_VIDEO_HEIGHT, CARD_HEIGHT); initialValues.put(KEY_AUDIO_CHANNEL_CONFIG, "2.0"); initialValues.put(KEY_PURCHASE_PRICE, mHelperContext.getString(R.string.buy_2)); initialValues.put(KEY_RENTAL_PRICE, mHelperContext.getString(R.string.rent_2)); initialValues.put(KEY_RATING_STYLE, Rating.RATING_5_STARS); initialValues.put(KEY_RATING_SCORE, 3.5f); initialValues.put(KEY_PRODUCTION_YEAR, 2014); initialValues.put(KEY_COLUMN_DURATION, 0); initialValues.put(KEY_ACTION, mHelperContext.getString(R.string.global_search)); return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues); } /** * Add an entry to the database for dummy deep link. * * @return rowId or -1 if failed */ public long addMovieForDeepLink(String title, String description, int icon, long duration, String production_year) { ContentValues initialValues = new ContentValues(); initialValues.put(KEY_NAME, title); initialValues.put(KEY_DESCRIPTION, description); initialValues.put(KEY_ICON, icon); initialValues.put(KEY_DATA_TYPE, "video/mp4"); initialValues.put(KEY_IS_LIVE, false); initialValues.put(KEY_VIDEO_WIDTH, 1280); initialValues.put(KEY_VIDEO_HEIGHT, 720); initialValues.put(KEY_AUDIO_CHANNEL_CONFIG, "2.0"); initialValues.put(KEY_PURCHASE_PRICE, "Free"); initialValues.put(KEY_RENTAL_PRICE, "Free"); initialValues.put(KEY_RATING_STYLE, Rating.RATING_5_STARS); initialValues.put(KEY_RATING_SCORE, 3.5f); initialValues.put(KEY_PRODUCTION_YEAR, production_year); initialValues.put(KEY_COLUMN_DURATION, duration); return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE); onCreate(db); } } }