/*
* Copyright (c) 2016 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.tvleanback.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.support.annotation.NonNull;
import java.util.HashMap;
/**
* VideoProvider is a ContentProvider that provides videos for the rest of applications.
*/
public class VideoProvider extends ContentProvider {
private static final UriMatcher sUriMatcher = buildUriMatcher();
private VideoDbHelper mOpenHelper;
// These codes are returned from sUriMatcher#match when the respective Uri matches.
private static final int VIDEO = 1;
private static final int VIDEO_WITH_CATEGORY = 2;
private static final int SEARCH_SUGGEST = 3;
private static final int REFRESH_SHORTCUT = 4;
private static final SQLiteQueryBuilder sVideosContainingQueryBuilder;
private static final String[] sVideosContainingQueryColumns;
private static final HashMap<String, String> sColumnMap = buildColumnMap();
private ContentResolver mContentResolver;
@Override
public boolean onCreate() {
Context context = getContext();
mContentResolver = context.getContentResolver();
mOpenHelper = new VideoDbHelper(context);
return true;
}
static {
sVideosContainingQueryBuilder = new SQLiteQueryBuilder();
sVideosContainingQueryBuilder.setTables(VideoContract.VideoEntry.TABLE_NAME);
sVideosContainingQueryBuilder.setProjectionMap(sColumnMap);
sVideosContainingQueryColumns = new String[]{
VideoContract.VideoEntry._ID,
VideoContract.VideoEntry.COLUMN_NAME,
VideoContract.VideoEntry.COLUMN_CATEGORY,
VideoContract.VideoEntry.COLUMN_DESC,
VideoContract.VideoEntry.COLUMN_VIDEO_URL,
VideoContract.VideoEntry.COLUMN_BG_IMAGE_URL,
VideoContract.VideoEntry.COLUMN_STUDIO,
VideoContract.VideoEntry.COLUMN_CARD_IMG,
VideoContract.VideoEntry.COLUMN_CONTENT_TYPE,
VideoContract.VideoEntry.COLUMN_IS_LIVE,
VideoContract.VideoEntry.COLUMN_VIDEO_WIDTH,
VideoContract.VideoEntry.COLUMN_VIDEO_HEIGHT,
VideoContract.VideoEntry.COLUMN_AUDIO_CHANNEL_CONFIG,
VideoContract.VideoEntry.COLUMN_PURCHASE_PRICE,
VideoContract.VideoEntry.COLUMN_RENTAL_PRICE,
VideoContract.VideoEntry.COLUMN_RATING_STYLE,
VideoContract.VideoEntry.COLUMN_RATING_SCORE,
VideoContract.VideoEntry.COLUMN_PRODUCTION_YEAR,
VideoContract.VideoEntry.COLUMN_DURATION,
VideoContract.VideoEntry.COLUMN_ACTION,
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID
};
}
static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = VideoContract.CONTENT_AUTHORITY;
// For each type of URI to add, create a corresponding code.
matcher.addURI(authority, VideoContract.PATH_VIDEO, VIDEO);
matcher.addURI(authority, VideoContract.PATH_VIDEO + "/*", VIDEO_WITH_CATEGORY);
// Search related URIs.
matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
matcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
return matcher;
}
private Cursor getSuggestions(String query) {
query = query.toLowerCase();
return sVideosContainingQueryBuilder.query(
mOpenHelper.getReadableDatabase(),
sVideosContainingQueryColumns,
VideoContract.VideoEntry.COLUMN_NAME + " LIKE ? OR " +
VideoContract.VideoEntry.COLUMN_DESC + " LIKE ?",
new String[]{"%" + query + "%", "%" + query + "%"},
null,
null,
null
);
}
private static HashMap<String, String> buildColumnMap() {
HashMap<String, String> map = new HashMap<>();
map.put(VideoContract.VideoEntry._ID, VideoContract.VideoEntry._ID);
map.put(VideoContract.VideoEntry.COLUMN_NAME, VideoContract.VideoEntry.COLUMN_NAME);
map.put(VideoContract.VideoEntry.COLUMN_DESC, VideoContract.VideoEntry.COLUMN_DESC);
map.put(VideoContract.VideoEntry.COLUMN_CATEGORY, VideoContract.VideoEntry.COLUMN_CATEGORY);
map.put(VideoContract.VideoEntry.COLUMN_VIDEO_URL,
VideoContract.VideoEntry.COLUMN_VIDEO_URL);
map.put(VideoContract.VideoEntry.COLUMN_BG_IMAGE_URL,
VideoContract.VideoEntry.COLUMN_BG_IMAGE_URL);
map.put(VideoContract.VideoEntry.COLUMN_CARD_IMG, VideoContract.VideoEntry.COLUMN_CARD_IMG);
map.put(VideoContract.VideoEntry.COLUMN_STUDIO, VideoContract.VideoEntry.COLUMN_STUDIO);
map.put(VideoContract.VideoEntry.COLUMN_CONTENT_TYPE,
VideoContract.VideoEntry.COLUMN_CONTENT_TYPE);
map.put(VideoContract.VideoEntry.COLUMN_IS_LIVE, VideoContract.VideoEntry.COLUMN_IS_LIVE);
map.put(VideoContract.VideoEntry.COLUMN_VIDEO_WIDTH,
VideoContract.VideoEntry.COLUMN_VIDEO_WIDTH);
map.put(VideoContract.VideoEntry.COLUMN_VIDEO_HEIGHT,
VideoContract.VideoEntry.COLUMN_VIDEO_HEIGHT);
map.put(VideoContract.VideoEntry.COLUMN_AUDIO_CHANNEL_CONFIG,
VideoContract.VideoEntry.COLUMN_AUDIO_CHANNEL_CONFIG);
map.put(VideoContract.VideoEntry.COLUMN_PURCHASE_PRICE,
VideoContract.VideoEntry.COLUMN_PURCHASE_PRICE);
map.put(VideoContract.VideoEntry.COLUMN_RENTAL_PRICE,
VideoContract.VideoEntry.COLUMN_RENTAL_PRICE);
map.put(VideoContract.VideoEntry.COLUMN_RATING_STYLE,
VideoContract.VideoEntry.COLUMN_RATING_STYLE);
map.put(VideoContract.VideoEntry.COLUMN_RATING_SCORE,
VideoContract.VideoEntry.COLUMN_RATING_SCORE);
map.put(VideoContract.VideoEntry.COLUMN_PRODUCTION_YEAR,
VideoContract.VideoEntry.COLUMN_PRODUCTION_YEAR);
map.put(VideoContract.VideoEntry.COLUMN_DURATION, VideoContract.VideoEntry.COLUMN_DURATION);
map.put(VideoContract.VideoEntry.COLUMN_ACTION, VideoContract.VideoEntry.COLUMN_ACTION);
map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, VideoContract.VideoEntry._ID + " AS " +
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
VideoContract.VideoEntry._ID + " AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
return map;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Cursor retCursor;
switch (sUriMatcher.match(uri)) {
case SEARCH_SUGGEST: {
String rawQuery = "";
if (selectionArgs != null && selectionArgs.length > 0) {
rawQuery = selectionArgs[0];
}
retCursor = getSuggestions(rawQuery);
break;
}
case VIDEO: {
retCursor = mOpenHelper.getReadableDatabase().query(
VideoContract.VideoEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
retCursor.setNotificationUri(mContentResolver, uri);
return retCursor;
}
@Override
public String getType(@NonNull Uri uri) {
switch (sUriMatcher.match(uri)) {
// The application is querying the db for its own contents.
case VIDEO_WITH_CATEGORY:
return VideoContract.VideoEntry.CONTENT_TYPE;
case VIDEO:
return VideoContract.VideoEntry.CONTENT_TYPE;
// The Android TV global search is querying our app for relevant content.
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
case REFRESH_SHORTCUT:
return SearchManager.SHORTCUT_MIME_TYPE;
// We aren't sure what is being asked of us.
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
final Uri returnUri;
final int match = sUriMatcher.match(uri);
switch (match) {
case VIDEO: {
long _id = mOpenHelper.getWritableDatabase().insert(
VideoContract.VideoEntry.TABLE_NAME, null, values);
if (_id > 0) {
returnUri = VideoContract.VideoEntry.buildVideoUri(_id);
} else {
throw new SQLException("Failed to insert row into " + uri);
}
break;
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
mContentResolver.notifyChange(uri, null);
return returnUri;
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
final int rowsDeleted;
if (selection == null) {
throw new UnsupportedOperationException("Cannot delete without selection specified.");
}
switch (sUriMatcher.match(uri)) {
case VIDEO: {
rowsDeleted = mOpenHelper.getWritableDatabase().delete(
VideoContract.VideoEntry.TABLE_NAME, selection, selectionArgs);
break;
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
if (rowsDeleted != 0) {
mContentResolver.notifyChange(uri, null);
}
return rowsDeleted;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
final int rowsUpdated;
switch (sUriMatcher.match(uri)) {
case VIDEO: {
rowsUpdated = mOpenHelper.getWritableDatabase().update(
VideoContract.VideoEntry.TABLE_NAME, values, selection, selectionArgs);
break;
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
if (rowsUpdated != 0) {
mContentResolver.notifyChange(uri, null);
}
return rowsUpdated;
}
@Override
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
switch (sUriMatcher.match(uri)) {
case VIDEO: {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int returnCount = 0;
db.beginTransaction();
try {
for (ContentValues value : values) {
long _id = db.insertWithOnConflict(VideoContract.VideoEntry.TABLE_NAME,
null, value, SQLiteDatabase.CONFLICT_REPLACE);
if (_id != -1) {
returnCount++;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
mContentResolver.notifyChange(uri, null);
return returnCount;
}
default: {
return super.bulkInsert(uri, values);
}
}
}
}