aboutsummaryrefslogtreecommitdiff
path: root/tests/common/src/com/android/tv/testing/FakeTvProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'tests/common/src/com/android/tv/testing/FakeTvProvider.java')
-rw-r--r--tests/common/src/com/android/tv/testing/FakeTvProvider.java2605
1 files changed, 2605 insertions, 0 deletions
diff --git a/tests/common/src/com/android/tv/testing/FakeTvProvider.java b/tests/common/src/com/android/tv/testing/FakeTvProvider.java
new file mode 100644
index 00000000..24c26f39
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/FakeTvProvider.java
@@ -0,0 +1,2605 @@
+/*
+ * Copyright (C) 2017 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.android.tv.testing;
+
+import android.annotation.SuppressLint;
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.content.SharedPreferences;
+import android.content.UriMatcher;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.preference.PreferenceManager;
+import android.provider.BaseColumns;
+import android.support.annotation.VisibleForTesting;
+import android.support.media.tv.TvContractCompat;
+import android.support.media.tv.TvContractCompat.BaseTvColumns;
+import android.support.media.tv.TvContractCompat.Channels;
+import android.support.media.tv.TvContractCompat.PreviewPrograms;
+import android.support.media.tv.TvContractCompat.Programs;
+import android.support.media.tv.TvContractCompat.Programs.Genres;
+import android.support.media.tv.TvContractCompat.RecordedPrograms;
+import android.support.media.tv.TvContractCompat.WatchNextPrograms;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.tv.util.SqlParams;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Fake TV content provider suitable for unit tests. The contract between this provider and
+ * applications is defined in {@link TvContractCompat}.
+ */
+// TODO(b/62143348): remove when error prone check fixed
+@SuppressWarnings({"AndroidApiChecker", "TryWithResources"})
+public class FakeTvProvider extends ContentProvider {
+ // TODO either make this a shadow or move it to the support library
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvProvider";
+
+ static final int DATABASE_VERSION = 34;
+ static final String SHARED_PREF_BLOCKED_PACKAGES_KEY = "blocked_packages";
+ static final String CHANNELS_TABLE = "channels";
+ static final String PROGRAMS_TABLE = "programs";
+ static final String RECORDED_PROGRAMS_TABLE = "recorded_programs";
+ static final String PREVIEW_PROGRAMS_TABLE = "preview_programs";
+ static final String WATCH_NEXT_PROGRAMS_TABLE = "watch_next_programs";
+ static final String WATCHED_PROGRAMS_TABLE = "watched_programs";
+ static final String PROGRAMS_TABLE_PACKAGE_NAME_INDEX = "programs_package_name_index";
+ static final String PROGRAMS_TABLE_CHANNEL_ID_INDEX = "programs_channel_id_index";
+ static final String PROGRAMS_TABLE_START_TIME_INDEX = "programs_start_time_index";
+ static final String PROGRAMS_TABLE_END_TIME_INDEX = "programs_end_time_index";
+ static final String WATCHED_PROGRAMS_TABLE_CHANNEL_ID_INDEX =
+ "watched_programs_channel_id_index";
+ // The internal column in the watched programs table to indicate whether the current log entry
+ // is consolidated or not. Unconsolidated entries may have columns with missing data.
+ static final String WATCHED_PROGRAMS_COLUMN_CONSOLIDATED = "consolidated";
+ static final String CHANNELS_COLUMN_LOGO = "logo";
+ private static final String DATABASE_NAME = "tv.db";
+ private static final String DELETED_CHANNELS_TABLE = "deleted_channels"; // Deprecated
+ private static final String DEFAULT_PROGRAMS_SORT_ORDER =
+ Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
+ private static final String DEFAULT_WATCHED_PROGRAMS_SORT_ORDER =
+ WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + " DESC";
+ private static final String CHANNELS_TABLE_INNER_JOIN_PROGRAMS_TABLE =
+ CHANNELS_TABLE
+ + " INNER JOIN "
+ + PROGRAMS_TABLE
+ + " ON ("
+ + CHANNELS_TABLE
+ + "."
+ + Channels._ID
+ + "="
+ + PROGRAMS_TABLE
+ + "."
+ + Programs.COLUMN_CHANNEL_ID
+ + ")";
+
+ // Operation names for createSqlParams().
+ private static final String OP_QUERY = "query";
+ private static final String OP_UPDATE = "update";
+ private static final String OP_DELETE = "delete";
+
+ private static final UriMatcher sUriMatcher;
+ private static final int MATCH_CHANNEL = 1;
+ private static final int MATCH_CHANNEL_ID = 2;
+ private static final int MATCH_CHANNEL_ID_LOGO = 3;
+ private static final int MATCH_PASSTHROUGH_ID = 4;
+ private static final int MATCH_PROGRAM = 5;
+ private static final int MATCH_PROGRAM_ID = 6;
+ private static final int MATCH_WATCHED_PROGRAM = 7;
+ private static final int MATCH_WATCHED_PROGRAM_ID = 8;
+ private static final int MATCH_RECORDED_PROGRAM = 9;
+ private static final int MATCH_RECORDED_PROGRAM_ID = 10;
+ private static final int MATCH_PREVIEW_PROGRAM = 11;
+ private static final int MATCH_PREVIEW_PROGRAM_ID = 12;
+ private static final int MATCH_WATCH_NEXT_PROGRAM = 13;
+ private static final int MATCH_WATCH_NEXT_PROGRAM_ID = 14;
+
+ private static final int MAX_LOGO_IMAGE_SIZE = 256;
+
+ private static final String EMPTY_STRING = "";
+
+ private static final Map<String, String> sChannelProjectionMap;
+ private static final Map<String, String> sProgramProjectionMap;
+ private static final Map<String, String> sWatchedProgramProjectionMap;
+ private static final Map<String, String> sRecordedProgramProjectionMap;
+ private static final Map<String, String> sPreviewProgramProjectionMap;
+ private static final Map<String, String> sWatchNextProgramProjectionMap;
+
+ // TvContract hidden
+ private static final String PARAM_PACKAGE = "package";
+ private static final String PARAM_PREVIEW = "preview";
+
+ private static boolean sInitialized;
+
+ static {
+ sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "channel", MATCH_CHANNEL);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "channel/#", MATCH_CHANNEL_ID);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "channel/#/logo", MATCH_CHANNEL_ID_LOGO);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "passthrough/*", MATCH_PASSTHROUGH_ID);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "program", MATCH_PROGRAM);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "program/#", MATCH_PROGRAM_ID);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "watched_program", MATCH_WATCHED_PROGRAM);
+ sUriMatcher.addURI(
+ TvContractCompat.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "recorded_program", MATCH_RECORDED_PROGRAM);
+ sUriMatcher.addURI(
+ TvContractCompat.AUTHORITY, "recorded_program/#", MATCH_RECORDED_PROGRAM_ID);
+ sUriMatcher.addURI(TvContractCompat.AUTHORITY, "preview_program", MATCH_PREVIEW_PROGRAM);
+ sUriMatcher.addURI(
+ TvContractCompat.AUTHORITY, "preview_program/#", MATCH_PREVIEW_PROGRAM_ID);
+ sUriMatcher.addURI(
+ TvContractCompat.AUTHORITY, "watch_next_program", MATCH_WATCH_NEXT_PROGRAM);
+ sUriMatcher.addURI(
+ TvContractCompat.AUTHORITY, "watch_next_program/#", MATCH_WATCH_NEXT_PROGRAM_ID);
+
+ sChannelProjectionMap = new HashMap<>();
+ sChannelProjectionMap.put(Channels._ID, CHANNELS_TABLE + "." + Channels._ID);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_PACKAGE_NAME, CHANNELS_TABLE + "." + Channels.COLUMN_PACKAGE_NAME);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INPUT_ID, CHANNELS_TABLE + "." + Channels.COLUMN_INPUT_ID);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_TYPE, CHANNELS_TABLE + "." + Channels.COLUMN_TYPE);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_SERVICE_TYPE, CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_TYPE);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_ORIGINAL_NETWORK_ID,
+ CHANNELS_TABLE + "." + Channels.COLUMN_ORIGINAL_NETWORK_ID);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_TRANSPORT_STREAM_ID,
+ CHANNELS_TABLE + "." + Channels.COLUMN_TRANSPORT_STREAM_ID);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_SERVICE_ID, CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_ID);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_DISPLAY_NUMBER,
+ CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NUMBER);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_DISPLAY_NAME, CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NAME);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_NETWORK_AFFILIATION,
+ CHANNELS_TABLE + "." + Channels.COLUMN_NETWORK_AFFILIATION);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_DESCRIPTION, CHANNELS_TABLE + "." + Channels.COLUMN_DESCRIPTION);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_VIDEO_FORMAT, CHANNELS_TABLE + "." + Channels.COLUMN_VIDEO_FORMAT);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_BROWSABLE, CHANNELS_TABLE + "." + Channels.COLUMN_BROWSABLE);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_SEARCHABLE, CHANNELS_TABLE + "." + Channels.COLUMN_SEARCHABLE);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_LOCKED, CHANNELS_TABLE + "." + Channels.COLUMN_LOCKED);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_APP_LINK_ICON_URI,
+ CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_ICON_URI);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_APP_LINK_POSTER_ART_URI,
+ CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_POSTER_ART_URI);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_APP_LINK_TEXT,
+ CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_TEXT);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_APP_LINK_COLOR,
+ CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_COLOR);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_APP_LINK_INTENT_URI,
+ CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_INTENT_URI);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_DATA,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_DATA);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_VERSION_NUMBER,
+ CHANNELS_TABLE + "." + Channels.COLUMN_VERSION_NUMBER);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_TRANSIENT, CHANNELS_TABLE + "." + Channels.COLUMN_TRANSIENT);
+ sChannelProjectionMap.put(
+ Channels.COLUMN_INTERNAL_PROVIDER_ID,
+ CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_ID);
+
+ sProgramProjectionMap = new HashMap<>();
+ sProgramProjectionMap.put(Programs._ID, Programs._ID);
+ sProgramProjectionMap.put(Programs.COLUMN_PACKAGE_NAME, Programs.COLUMN_PACKAGE_NAME);
+ sProgramProjectionMap.put(Programs.COLUMN_CHANNEL_ID, Programs.COLUMN_CHANNEL_ID);
+ sProgramProjectionMap.put(Programs.COLUMN_TITLE, Programs.COLUMN_TITLE);
+ // COLUMN_SEASON_NUMBER is deprecated. Return COLUMN_SEASON_DISPLAY_NUMBER instead.
+ sProgramProjectionMap.put(
+ Programs.COLUMN_SEASON_NUMBER,
+ Programs.COLUMN_SEASON_DISPLAY_NUMBER + " AS " + Programs.COLUMN_SEASON_NUMBER);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_SEASON_DISPLAY_NUMBER, Programs.COLUMN_SEASON_DISPLAY_NUMBER);
+ sProgramProjectionMap.put(Programs.COLUMN_SEASON_TITLE, Programs.COLUMN_SEASON_TITLE);
+ // COLUMN_EPISODE_NUMBER is deprecated. Return COLUMN_EPISODE_DISPLAY_NUMBER instead.
+ sProgramProjectionMap.put(
+ Programs.COLUMN_EPISODE_NUMBER,
+ Programs.COLUMN_EPISODE_DISPLAY_NUMBER + " AS " + Programs.COLUMN_EPISODE_NUMBER);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_EPISODE_DISPLAY_NUMBER, Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
+ sProgramProjectionMap.put(Programs.COLUMN_EPISODE_TITLE, Programs.COLUMN_EPISODE_TITLE);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_START_TIME_UTC_MILLIS, Programs.COLUMN_START_TIME_UTC_MILLIS);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_END_TIME_UTC_MILLIS, Programs.COLUMN_END_TIME_UTC_MILLIS);
+ sProgramProjectionMap.put(Programs.COLUMN_BROADCAST_GENRE, Programs.COLUMN_BROADCAST_GENRE);
+ sProgramProjectionMap.put(Programs.COLUMN_CANONICAL_GENRE, Programs.COLUMN_CANONICAL_GENRE);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_SHORT_DESCRIPTION, Programs.COLUMN_SHORT_DESCRIPTION);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_LONG_DESCRIPTION, Programs.COLUMN_LONG_DESCRIPTION);
+ sProgramProjectionMap.put(Programs.COLUMN_VIDEO_WIDTH, Programs.COLUMN_VIDEO_WIDTH);
+ sProgramProjectionMap.put(Programs.COLUMN_VIDEO_HEIGHT, Programs.COLUMN_VIDEO_HEIGHT);
+ sProgramProjectionMap.put(Programs.COLUMN_AUDIO_LANGUAGE, Programs.COLUMN_AUDIO_LANGUAGE);
+ sProgramProjectionMap.put(Programs.COLUMN_CONTENT_RATING, Programs.COLUMN_CONTENT_RATING);
+ sProgramProjectionMap.put(Programs.COLUMN_POSTER_ART_URI, Programs.COLUMN_POSTER_ART_URI);
+ sProgramProjectionMap.put(Programs.COLUMN_THUMBNAIL_URI, Programs.COLUMN_THUMBNAIL_URI);
+ sProgramProjectionMap.put(Programs.COLUMN_SEARCHABLE, Programs.COLUMN_SEARCHABLE);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_RECORDING_PROHIBITED, Programs.COLUMN_RECORDING_PROHIBITED);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_INTERNAL_PROVIDER_DATA, Programs.COLUMN_INTERNAL_PROVIDER_DATA);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_INTERNAL_PROVIDER_FLAG1, Programs.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_INTERNAL_PROVIDER_FLAG2, Programs.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_INTERNAL_PROVIDER_FLAG3, Programs.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_INTERNAL_PROVIDER_FLAG4, Programs.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ sProgramProjectionMap.put(Programs.COLUMN_VERSION_NUMBER, Programs.COLUMN_VERSION_NUMBER);
+ sProgramProjectionMap.put(
+ Programs.COLUMN_REVIEW_RATING_STYLE, Programs.COLUMN_REVIEW_RATING_STYLE);
+ sProgramProjectionMap.put(Programs.COLUMN_REVIEW_RATING, Programs.COLUMN_REVIEW_RATING);
+
+ sWatchedProgramProjectionMap = new HashMap<>();
+ sWatchedProgramProjectionMap.put(WatchedPrograms._ID, WatchedPrograms._ID);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
+ WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
+ WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_CHANNEL_ID, WatchedPrograms.COLUMN_CHANNEL_ID);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_TITLE, WatchedPrograms.COLUMN_TITLE);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
+ WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_DESCRIPTION, WatchedPrograms.COLUMN_DESCRIPTION);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
+ WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS);
+ sWatchedProgramProjectionMap.put(
+ WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
+ WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN);
+ sWatchedProgramProjectionMap.put(
+ WATCHED_PROGRAMS_COLUMN_CONSOLIDATED, WATCHED_PROGRAMS_COLUMN_CONSOLIDATED);
+
+ sRecordedProgramProjectionMap = new HashMap<>();
+ sRecordedProgramProjectionMap.put(RecordedPrograms._ID, RecordedPrograms._ID);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_PACKAGE_NAME, RecordedPrograms.COLUMN_PACKAGE_NAME);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INPUT_ID, RecordedPrograms.COLUMN_INPUT_ID);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_CHANNEL_ID, RecordedPrograms.COLUMN_CHANNEL_ID);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_TITLE, RecordedPrograms.COLUMN_TITLE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
+ RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_SEASON_TITLE, RecordedPrograms.COLUMN_SEASON_TITLE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
+ RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_EPISODE_TITLE, RecordedPrograms.COLUMN_EPISODE_TITLE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_BROADCAST_GENRE, RecordedPrograms.COLUMN_BROADCAST_GENRE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_CANONICAL_GENRE, RecordedPrograms.COLUMN_CANONICAL_GENRE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_SHORT_DESCRIPTION,
+ RecordedPrograms.COLUMN_SHORT_DESCRIPTION);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_LONG_DESCRIPTION, RecordedPrograms.COLUMN_LONG_DESCRIPTION);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_VIDEO_WIDTH, RecordedPrograms.COLUMN_VIDEO_WIDTH);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_VIDEO_HEIGHT, RecordedPrograms.COLUMN_VIDEO_HEIGHT);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_AUDIO_LANGUAGE, RecordedPrograms.COLUMN_AUDIO_LANGUAGE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_CONTENT_RATING, RecordedPrograms.COLUMN_CONTENT_RATING);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_POSTER_ART_URI, RecordedPrograms.COLUMN_POSTER_ART_URI);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_THUMBNAIL_URI, RecordedPrograms.COLUMN_THUMBNAIL_URI);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_SEARCHABLE, RecordedPrograms.COLUMN_SEARCHABLE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_RECORDING_DATA_URI,
+ RecordedPrograms.COLUMN_RECORDING_DATA_URI);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_RECORDING_DATA_BYTES,
+ RecordedPrograms.COLUMN_RECORDING_DATA_BYTES);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
+ RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_VERSION_NUMBER, RecordedPrograms.COLUMN_VERSION_NUMBER);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_REVIEW_RATING_STYLE,
+ RecordedPrograms.COLUMN_REVIEW_RATING_STYLE);
+ sRecordedProgramProjectionMap.put(
+ RecordedPrograms.COLUMN_REVIEW_RATING, RecordedPrograms.COLUMN_REVIEW_RATING);
+
+ sPreviewProgramProjectionMap = new HashMap<>();
+ sPreviewProgramProjectionMap.put(PreviewPrograms._ID, PreviewPrograms._ID);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_PACKAGE_NAME, PreviewPrograms.COLUMN_PACKAGE_NAME);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_CHANNEL_ID, PreviewPrograms.COLUMN_CHANNEL_ID);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_TITLE, PreviewPrograms.COLUMN_TITLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
+ PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_SEASON_TITLE, PreviewPrograms.COLUMN_SEASON_TITLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
+ PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_EPISODE_TITLE, PreviewPrograms.COLUMN_EPISODE_TITLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_CANONICAL_GENRE, PreviewPrograms.COLUMN_CANONICAL_GENRE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_SHORT_DESCRIPTION, PreviewPrograms.COLUMN_SHORT_DESCRIPTION);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_LONG_DESCRIPTION, PreviewPrograms.COLUMN_LONG_DESCRIPTION);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_VIDEO_WIDTH, PreviewPrograms.COLUMN_VIDEO_WIDTH);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_VIDEO_HEIGHT, PreviewPrograms.COLUMN_VIDEO_HEIGHT);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_AUDIO_LANGUAGE, PreviewPrograms.COLUMN_AUDIO_LANGUAGE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_CONTENT_RATING, PreviewPrograms.COLUMN_CONTENT_RATING);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_POSTER_ART_URI, PreviewPrograms.COLUMN_POSTER_ART_URI);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_THUMBNAIL_URI, PreviewPrograms.COLUMN_THUMBNAIL_URI);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_SEARCHABLE, PreviewPrograms.COLUMN_SEARCHABLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_VERSION_NUMBER, PreviewPrograms.COLUMN_VERSION_NUMBER);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID,
+ PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI, PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS,
+ PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_DURATION_MILLIS, PreviewPrograms.COLUMN_DURATION_MILLIS);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTENT_URI, PreviewPrograms.COLUMN_INTENT_URI);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_WEIGHT, PreviewPrograms.COLUMN_WEIGHT);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_TRANSIENT, PreviewPrograms.COLUMN_TRANSIENT);
+ sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_TYPE, PreviewPrograms.COLUMN_TYPE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO,
+ PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO,
+ PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_LOGO_URI, PreviewPrograms.COLUMN_LOGO_URI);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_AVAILABILITY, PreviewPrograms.COLUMN_AVAILABILITY);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_STARTING_PRICE, PreviewPrograms.COLUMN_STARTING_PRICE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_OFFER_PRICE, PreviewPrograms.COLUMN_OFFER_PRICE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_RELEASE_DATE, PreviewPrograms.COLUMN_RELEASE_DATE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_ITEM_COUNT, PreviewPrograms.COLUMN_ITEM_COUNT);
+ sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LIVE, PreviewPrograms.COLUMN_LIVE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERACTION_TYPE, PreviewPrograms.COLUMN_INTERACTION_TYPE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_INTERACTION_COUNT, PreviewPrograms.COLUMN_INTERACTION_COUNT);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_AUTHOR, PreviewPrograms.COLUMN_AUTHOR);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_REVIEW_RATING_STYLE,
+ PreviewPrograms.COLUMN_REVIEW_RATING_STYLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_REVIEW_RATING, PreviewPrograms.COLUMN_REVIEW_RATING);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_BROWSABLE, PreviewPrograms.COLUMN_BROWSABLE);
+ sPreviewProgramProjectionMap.put(
+ PreviewPrograms.COLUMN_CONTENT_ID, PreviewPrograms.COLUMN_CONTENT_ID);
+
+ sWatchNextProgramProjectionMap = new HashMap<>();
+ sWatchNextProgramProjectionMap.put(WatchNextPrograms._ID, WatchNextPrograms._ID);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_PACKAGE_NAME, WatchNextPrograms.COLUMN_PACKAGE_NAME);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_TITLE, WatchNextPrograms.COLUMN_TITLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
+ WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_SEASON_TITLE, WatchNextPrograms.COLUMN_SEASON_TITLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
+ WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_EPISODE_TITLE, WatchNextPrograms.COLUMN_EPISODE_TITLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_CANONICAL_GENRE, WatchNextPrograms.COLUMN_CANONICAL_GENRE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_SHORT_DESCRIPTION,
+ WatchNextPrograms.COLUMN_SHORT_DESCRIPTION);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_LONG_DESCRIPTION,
+ WatchNextPrograms.COLUMN_LONG_DESCRIPTION);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_VIDEO_WIDTH, WatchNextPrograms.COLUMN_VIDEO_WIDTH);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_VIDEO_HEIGHT, WatchNextPrograms.COLUMN_VIDEO_HEIGHT);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_AUDIO_LANGUAGE, WatchNextPrograms.COLUMN_AUDIO_LANGUAGE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_CONTENT_RATING, WatchNextPrograms.COLUMN_CONTENT_RATING);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_POSTER_ART_URI, WatchNextPrograms.COLUMN_POSTER_ART_URI);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_THUMBNAIL_URI, WatchNextPrograms.COLUMN_THUMBNAIL_URI);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_SEARCHABLE, WatchNextPrograms.COLUMN_SEARCHABLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_VERSION_NUMBER, WatchNextPrograms.COLUMN_VERSION_NUMBER);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID,
+ WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI,
+ WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS,
+ WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_DURATION_MILLIS, WatchNextPrograms.COLUMN_DURATION_MILLIS);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTENT_URI, WatchNextPrograms.COLUMN_INTENT_URI);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_TRANSIENT, WatchNextPrograms.COLUMN_TRANSIENT);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_TYPE, WatchNextPrograms.COLUMN_TYPE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE, WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO,
+ WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO,
+ WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_LOGO_URI, WatchNextPrograms.COLUMN_LOGO_URI);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_AVAILABILITY, WatchNextPrograms.COLUMN_AVAILABILITY);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_STARTING_PRICE, WatchNextPrograms.COLUMN_STARTING_PRICE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_OFFER_PRICE, WatchNextPrograms.COLUMN_OFFER_PRICE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_RELEASE_DATE, WatchNextPrograms.COLUMN_RELEASE_DATE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_ITEM_COUNT, WatchNextPrograms.COLUMN_ITEM_COUNT);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_LIVE, WatchNextPrograms.COLUMN_LIVE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERACTION_TYPE,
+ WatchNextPrograms.COLUMN_INTERACTION_TYPE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_INTERACTION_COUNT,
+ WatchNextPrograms.COLUMN_INTERACTION_COUNT);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_AUTHOR, WatchNextPrograms.COLUMN_AUTHOR);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE,
+ WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_REVIEW_RATING, WatchNextPrograms.COLUMN_REVIEW_RATING);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_BROWSABLE, WatchNextPrograms.COLUMN_BROWSABLE);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_CONTENT_ID, WatchNextPrograms.COLUMN_CONTENT_ID);
+ sWatchNextProgramProjectionMap.put(
+ WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS,
+ WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS);
+ }
+
+ // Mapping from broadcast genre to canonical genre.
+ private static Map<String, String> sGenreMap;
+
+ private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
+
+ private static final String PERMISSION_ACCESS_ALL_EPG_DATA =
+ "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA";
+
+ private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
+ "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";
+
+ private static final String CREATE_RECORDED_PROGRAMS_TABLE_SQL =
+ "CREATE TABLE "
+ + RECORDED_PROGRAMS_TABLE
+ + " ("
+ + RecordedPrograms._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + RecordedPrograms.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + RecordedPrograms.COLUMN_INPUT_ID
+ + " TEXT NOT NULL,"
+ + RecordedPrograms.COLUMN_CHANNEL_ID
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_TITLE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_SEASON_TITLE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_EPISODE_TITLE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_BROADCAST_GENRE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_CANONICAL_GENRE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_SHORT_DESCRIPTION
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_LONG_DESCRIPTION
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_VIDEO_WIDTH
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_VIDEO_HEIGHT
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_AUDIO_LANGUAGE
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_CONTENT_RATING
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_POSTER_ART_URI
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_THUMBNAIL_URI
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + RecordedPrograms.COLUMN_RECORDING_DATA_URI
+ + " TEXT,"
+ + RecordedPrograms.COLUMN_RECORDING_DATA_BYTES
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA
+ + " BLOB,"
+ + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_VERSION_NUMBER
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER,"
+ + RecordedPrograms.COLUMN_REVIEW_RATING
+ + " TEXT,"
+ + "FOREIGN KEY("
+ + RecordedPrograms.COLUMN_CHANNEL_ID
+ + ") "
+ + "REFERENCES "
+ + CHANNELS_TABLE
+ + "("
+ + Channels._ID
+ + ") "
+ + "ON UPDATE CASCADE ON DELETE SET NULL);";
+
+ private static final String CREATE_PREVIEW_PROGRAMS_TABLE_SQL =
+ "CREATE TABLE "
+ + PREVIEW_PROGRAMS_TABLE
+ + " ("
+ + PreviewPrograms._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + PreviewPrograms.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + PreviewPrograms.COLUMN_CHANNEL_ID
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_TITLE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_SEASON_TITLE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_EPISODE_TITLE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_CANONICAL_GENRE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_SHORT_DESCRIPTION
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_LONG_DESCRIPTION
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_VIDEO_WIDTH
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_VIDEO_HEIGHT
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_AUDIO_LANGUAGE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_CONTENT_RATING
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_POSTER_ART_URI
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_THUMBNAIL_URI
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA
+ + " BLOB,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_VERSION_NUMBER
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_DURATION_MILLIS
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTENT_URI
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_WEIGHT
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_TRANSIENT
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + PreviewPrograms.COLUMN_TYPE
+ + " INTEGER NOT NULL,"
+ + PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_LOGO_URI
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_AVAILABILITY
+ + " INTERGER,"
+ + PreviewPrograms.COLUMN_STARTING_PRICE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_OFFER_PRICE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_RELEASE_DATE
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_ITEM_COUNT
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_LIVE
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + PreviewPrograms.COLUMN_INTERACTION_TYPE
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_INTERACTION_COUNT
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_AUTHOR
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER,"
+ + PreviewPrograms.COLUMN_REVIEW_RATING
+ + " TEXT,"
+ + PreviewPrograms.COLUMN_BROWSABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + PreviewPrograms.COLUMN_CONTENT_ID
+ + " TEXT,"
+ + "FOREIGN KEY("
+ + PreviewPrograms.COLUMN_CHANNEL_ID
+ + ","
+ + PreviewPrograms.COLUMN_PACKAGE_NAME
+ + ") REFERENCES "
+ + CHANNELS_TABLE
+ + "("
+ + Channels._ID
+ + ","
+ + Channels.COLUMN_PACKAGE_NAME
+ + ") ON UPDATE CASCADE ON DELETE CASCADE"
+ + ");";
+ private static final String CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL =
+ "CREATE INDEX preview_programs_package_name_index ON "
+ + PREVIEW_PROGRAMS_TABLE
+ + "("
+ + PreviewPrograms.COLUMN_PACKAGE_NAME
+ + ");";
+ private static final String CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL =
+ "CREATE INDEX preview_programs_id_index ON "
+ + PREVIEW_PROGRAMS_TABLE
+ + "("
+ + PreviewPrograms.COLUMN_CHANNEL_ID
+ + ");";
+ private static final String CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL =
+ "CREATE TABLE "
+ + WATCH_NEXT_PROGRAMS_TABLE
+ + " ("
+ + WatchNextPrograms._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + WatchNextPrograms.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + WatchNextPrograms.COLUMN_TITLE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_SEASON_TITLE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_EPISODE_TITLE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_CANONICAL_GENRE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_SHORT_DESCRIPTION
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_LONG_DESCRIPTION
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_VIDEO_WIDTH
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_VIDEO_HEIGHT
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_AUDIO_LANGUAGE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_CONTENT_RATING
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_POSTER_ART_URI
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_THUMBNAIL_URI
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA
+ + " BLOB,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_VERSION_NUMBER
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_DURATION_MILLIS
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTENT_URI
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_TRANSIENT
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + WatchNextPrograms.COLUMN_TYPE
+ + " INTEGER NOT NULL,"
+ + WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_LOGO_URI
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_AVAILABILITY
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_STARTING_PRICE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_OFFER_PRICE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_RELEASE_DATE
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_ITEM_COUNT
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_LIVE
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + WatchNextPrograms.COLUMN_INTERACTION_TYPE
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_INTERACTION_COUNT
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_AUTHOR
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER,"
+ + WatchNextPrograms.COLUMN_REVIEW_RATING
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_BROWSABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + WatchNextPrograms.COLUMN_CONTENT_ID
+ + " TEXT,"
+ + WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS
+ + " INTEGER"
+ + ");";
+ private static final String CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL =
+ "CREATE INDEX watch_next_programs_package_name_index ON "
+ + WATCH_NEXT_PROGRAMS_TABLE
+ + "("
+ + WatchNextPrograms.COLUMN_PACKAGE_NAME
+ + ");";
+
+ private String mCallingPackage = "com.android.tv";
+
+ static class DatabaseHelper extends SQLiteOpenHelper {
+ private Context mContext;
+
+ public static synchronized DatabaseHelper createInstance(Context context) {
+ return new DatabaseHelper(context);
+ }
+
+ private DatabaseHelper(Context context) {
+ this(context, DATABASE_NAME, DATABASE_VERSION);
+ }
+
+ @VisibleForTesting
+ DatabaseHelper(Context context, String databaseName, int databaseVersion) {
+ super(context, databaseName, null, databaseVersion);
+ mContext = context;
+ }
+
+ @Override
+ public void onConfigure(SQLiteDatabase db) {
+ db.setForeignKeyConstraintsEnabled(true);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ if (DEBUG) {
+ Log.d(TAG, "Creating database");
+ }
+ // Set up the database schema.
+ db.execSQL(
+ "CREATE TABLE "
+ + CHANNELS_TABLE
+ + " ("
+ + Channels._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + Channels.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + Channels.COLUMN_INPUT_ID
+ + " TEXT NOT NULL,"
+ + Channels.COLUMN_TYPE
+ + " TEXT NOT NULL DEFAULT '"
+ + Channels.TYPE_OTHER
+ + "',"
+ + Channels.COLUMN_SERVICE_TYPE
+ + " TEXT NOT NULL DEFAULT '"
+ + Channels.SERVICE_TYPE_AUDIO_VIDEO
+ + "',"
+ + Channels.COLUMN_ORIGINAL_NETWORK_ID
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_TRANSPORT_STREAM_ID
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_SERVICE_ID
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_DISPLAY_NUMBER
+ + " TEXT,"
+ + Channels.COLUMN_DISPLAY_NAME
+ + " TEXT,"
+ + Channels.COLUMN_NETWORK_AFFILIATION
+ + " TEXT,"
+ + Channels.COLUMN_DESCRIPTION
+ + " TEXT,"
+ + Channels.COLUMN_VIDEO_FORMAT
+ + " TEXT,"
+ + Channels.COLUMN_BROWSABLE
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + Channels.COLUMN_LOCKED
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_APP_LINK_ICON_URI
+ + " TEXT,"
+ + Channels.COLUMN_APP_LINK_POSTER_ART_URI
+ + " TEXT,"
+ + Channels.COLUMN_APP_LINK_TEXT
+ + " TEXT,"
+ + Channels.COLUMN_APP_LINK_COLOR
+ + " INTEGER,"
+ + Channels.COLUMN_APP_LINK_INTENT_URI
+ + " TEXT,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_DATA
+ + " BLOB,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER,"
+ + CHANNELS_COLUMN_LOGO
+ + " BLOB,"
+ + Channels.COLUMN_VERSION_NUMBER
+ + " INTEGER,"
+ + Channels.COLUMN_TRANSIENT
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Channels.COLUMN_INTERNAL_PROVIDER_ID
+ + " TEXT,"
+ // Needed for foreign keys in other tables.
+ + "UNIQUE("
+ + Channels._ID
+ + ","
+ + Channels.COLUMN_PACKAGE_NAME
+ + ")"
+ + ");");
+ db.execSQL(
+ "CREATE TABLE "
+ + PROGRAMS_TABLE
+ + " ("
+ + Programs._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + Programs.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + Programs.COLUMN_CHANNEL_ID
+ + " INTEGER,"
+ + Programs.COLUMN_TITLE
+ + " TEXT,"
+ + Programs.COLUMN_SEASON_DISPLAY_NUMBER
+ + " TEXT,"
+ + Programs.COLUMN_SEASON_TITLE
+ + " TEXT,"
+ + Programs.COLUMN_EPISODE_DISPLAY_NUMBER
+ + " TEXT,"
+ + Programs.COLUMN_EPISODE_TITLE
+ + " TEXT,"
+ + Programs.COLUMN_START_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + Programs.COLUMN_END_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + Programs.COLUMN_BROADCAST_GENRE
+ + " TEXT,"
+ + Programs.COLUMN_CANONICAL_GENRE
+ + " TEXT,"
+ + Programs.COLUMN_SHORT_DESCRIPTION
+ + " TEXT,"
+ + Programs.COLUMN_LONG_DESCRIPTION
+ + " TEXT,"
+ + Programs.COLUMN_VIDEO_WIDTH
+ + " INTEGER,"
+ + Programs.COLUMN_VIDEO_HEIGHT
+ + " INTEGER,"
+ + Programs.COLUMN_AUDIO_LANGUAGE
+ + " TEXT,"
+ + Programs.COLUMN_CONTENT_RATING
+ + " TEXT,"
+ + Programs.COLUMN_POSTER_ART_URI
+ + " TEXT,"
+ + Programs.COLUMN_THUMBNAIL_URI
+ + " TEXT,"
+ + Programs.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1,"
+ + Programs.COLUMN_RECORDING_PROHIBITED
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + Programs.COLUMN_INTERNAL_PROVIDER_DATA
+ + " BLOB,"
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER,"
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER,"
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER,"
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER,"
+ + Programs.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER,"
+ + Programs.COLUMN_REVIEW_RATING
+ + " TEXT,"
+ + Programs.COLUMN_VERSION_NUMBER
+ + " INTEGER,"
+ + "FOREIGN KEY("
+ + Programs.COLUMN_CHANNEL_ID
+ + ","
+ + Programs.COLUMN_PACKAGE_NAME
+ + ") REFERENCES "
+ + CHANNELS_TABLE
+ + "("
+ + Channels._ID
+ + ","
+ + Channels.COLUMN_PACKAGE_NAME
+ + ") ON UPDATE CASCADE ON DELETE CASCADE"
+ + ");");
+ db.execSQL(
+ "CREATE INDEX "
+ + PROGRAMS_TABLE_PACKAGE_NAME_INDEX
+ + " ON "
+ + PROGRAMS_TABLE
+ + "("
+ + Programs.COLUMN_PACKAGE_NAME
+ + ");");
+ db.execSQL(
+ "CREATE INDEX "
+ + PROGRAMS_TABLE_CHANNEL_ID_INDEX
+ + " ON "
+ + PROGRAMS_TABLE
+ + "("
+ + Programs.COLUMN_CHANNEL_ID
+ + ");");
+ db.execSQL(
+ "CREATE INDEX "
+ + PROGRAMS_TABLE_START_TIME_INDEX
+ + " ON "
+ + PROGRAMS_TABLE
+ + "("
+ + Programs.COLUMN_START_TIME_UTC_MILLIS
+ + ");");
+ db.execSQL(
+ "CREATE INDEX "
+ + PROGRAMS_TABLE_END_TIME_INDEX
+ + " ON "
+ + PROGRAMS_TABLE
+ + "("
+ + Programs.COLUMN_END_TIME_UTC_MILLIS
+ + ");");
+ db.execSQL(
+ "CREATE TABLE "
+ + WATCHED_PROGRAMS_TABLE
+ + " ("
+ + WatchedPrograms._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + WatchedPrograms.COLUMN_PACKAGE_NAME
+ + " TEXT NOT NULL,"
+ + WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + WatchedPrograms.COLUMN_CHANNEL_ID
+ + " INTEGER,"
+ + WatchedPrograms.COLUMN_TITLE
+ + " TEXT,"
+ + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS
+ + " INTEGER,"
+ + WatchedPrograms.COLUMN_DESCRIPTION
+ + " TEXT,"
+ + WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS
+ + " TEXT,"
+ + WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN
+ + " TEXT NOT NULL,"
+ + WATCHED_PROGRAMS_COLUMN_CONSOLIDATED
+ + " INTEGER NOT NULL DEFAULT 0,"
+ + "FOREIGN KEY("
+ + WatchedPrograms.COLUMN_CHANNEL_ID
+ + ","
+ + WatchedPrograms.COLUMN_PACKAGE_NAME
+ + ") REFERENCES "
+ + CHANNELS_TABLE
+ + "("
+ + Channels._ID
+ + ","
+ + Channels.COLUMN_PACKAGE_NAME
+ + ") ON UPDATE CASCADE ON DELETE CASCADE"
+ + ");");
+ db.execSQL(
+ "CREATE INDEX "
+ + WATCHED_PROGRAMS_TABLE_CHANNEL_ID_INDEX
+ + " ON "
+ + WATCHED_PROGRAMS_TABLE
+ + "("
+ + WatchedPrograms.COLUMN_CHANNEL_ID
+ + ");");
+ db.execSQL(CREATE_RECORDED_PROGRAMS_TABLE_SQL);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_TABLE_SQL);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL);
+ db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL);
+ db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < 23) {
+ Log.i(
+ TAG,
+ "Upgrading from version "
+ + oldVersion
+ + " to "
+ + newVersion
+ + ", data will be lost!");
+ db.execSQL("DROP TABLE IF EXISTS " + DELETED_CHANNELS_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + WATCHED_PROGRAMS_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + PROGRAMS_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + CHANNELS_TABLE);
+
+ onCreate(db);
+ return;
+ }
+
+ Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion + ".");
+ if (oldVersion <= 23) {
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER;");
+ }
+ if (oldVersion <= 24) {
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG1
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG2
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG3
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_INTERNAL_PROVIDER_FLAG4
+ + " INTEGER;");
+ }
+ if (oldVersion <= 25) {
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_APP_LINK_ICON_URI
+ + " TEXT;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_APP_LINK_POSTER_ART_URI
+ + " TEXT;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_APP_LINK_TEXT
+ + " TEXT;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_APP_LINK_COLOR
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_APP_LINK_INTENT_URI
+ + " TEXT;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_SEARCHABLE
+ + " INTEGER NOT NULL DEFAULT 1;");
+ }
+ if (oldVersion <= 28) {
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_SEASON_TITLE
+ + " TEXT;");
+ migrateIntegerColumnToTextColumn(
+ db,
+ PROGRAMS_TABLE,
+ Programs.COLUMN_SEASON_NUMBER,
+ Programs.COLUMN_SEASON_DISPLAY_NUMBER);
+ migrateIntegerColumnToTextColumn(
+ db,
+ PROGRAMS_TABLE,
+ Programs.COLUMN_EPISODE_NUMBER,
+ Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
+ }
+ if (oldVersion <= 29) {
+ db.execSQL("DROP TABLE IF EXISTS " + RECORDED_PROGRAMS_TABLE);
+ db.execSQL(CREATE_RECORDED_PROGRAMS_TABLE_SQL);
+ }
+ if (oldVersion <= 30) {
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_RECORDING_PROHIBITED
+ + " INTEGER NOT NULL DEFAULT 0;");
+ }
+ if (oldVersion <= 32) {
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_TRANSIENT
+ + " INTEGER NOT NULL DEFAULT 0;");
+ db.execSQL(
+ "ALTER TABLE "
+ + CHANNELS_TABLE
+ + " ADD "
+ + Channels.COLUMN_INTERNAL_PROVIDER_ID
+ + " TEXT;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + PROGRAMS_TABLE
+ + " ADD "
+ + Programs.COLUMN_REVIEW_RATING
+ + " TEXT;");
+ if (oldVersion > 29) {
+ db.execSQL(
+ "ALTER TABLE "
+ + RECORDED_PROGRAMS_TABLE
+ + " ADD "
+ + RecordedPrograms.COLUMN_REVIEW_RATING_STYLE
+ + " INTEGER;");
+ db.execSQL(
+ "ALTER TABLE "
+ + RECORDED_PROGRAMS_TABLE
+ + " ADD "
+ + RecordedPrograms.COLUMN_REVIEW_RATING
+ + " TEXT;");
+ }
+ }
+ if (oldVersion <= 33) {
+ db.execSQL("DROP TABLE IF EXISTS " + PREVIEW_PROGRAMS_TABLE);
+ db.execSQL("DROP TABLE IF EXISTS " + WATCH_NEXT_PROGRAMS_TABLE);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_TABLE_SQL);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL);
+ db.execSQL(CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL);
+ db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL);
+ db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL);
+ }
+ Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion + " is done.");
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ // Call a static method on the TvProvider because changes to sInitialized must
+ // be guarded by a lock on the class.
+ initOnOpenIfNeeded(mContext, db);
+ }
+
+ private static void migrateIntegerColumnToTextColumn(
+ SQLiteDatabase db, String table, String integerColumn, String textColumn) {
+ db.execSQL("ALTER TABLE " + table + " ADD " + textColumn + " TEXT;");
+ db.execSQL(
+ "UPDATE "
+ + table
+ + " SET "
+ + textColumn
+ + " = CAST("
+ + integerColumn
+ + " AS TEXT);");
+ }
+ }
+
+ private DatabaseHelper mOpenHelper;
+ private static SharedPreferences sBlockedPackagesSharedPreference;
+ private static Map<String, Boolean> sBlockedPackages;
+
+ @Override
+ public boolean onCreate() {
+ if (DEBUG) {
+ Log.d(TAG, "Creating TvProvider");
+ }
+ mOpenHelper = DatabaseHelper.createInstance(getContext());
+ return true;
+ }
+
+ @VisibleForTesting
+ String getCallingPackage_() {
+ return mCallingPackage;
+ }
+
+ public void setCallingPackage(String packageName) {
+ mCallingPackage = packageName;
+ }
+
+ void setOpenHelper(DatabaseHelper helper) {
+ mOpenHelper = helper;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ switch (sUriMatcher.match(uri)) {
+ case MATCH_CHANNEL:
+ return Channels.CONTENT_TYPE;
+ case MATCH_CHANNEL_ID:
+ return Channels.CONTENT_ITEM_TYPE;
+ case MATCH_CHANNEL_ID_LOGO:
+ return "image/png";
+ case MATCH_PASSTHROUGH_ID:
+ return Channels.CONTENT_ITEM_TYPE;
+ case MATCH_PROGRAM:
+ return Programs.CONTENT_TYPE;
+ case MATCH_PROGRAM_ID:
+ return Programs.CONTENT_ITEM_TYPE;
+ case MATCH_WATCHED_PROGRAM:
+ return WatchedPrograms.CONTENT_TYPE;
+ case MATCH_WATCHED_PROGRAM_ID:
+ return WatchedPrograms.CONTENT_ITEM_TYPE;
+ case MATCH_RECORDED_PROGRAM:
+ return RecordedPrograms.CONTENT_TYPE;
+ case MATCH_RECORDED_PROGRAM_ID:
+ return RecordedPrograms.CONTENT_ITEM_TYPE;
+ case MATCH_PREVIEW_PROGRAM:
+ return PreviewPrograms.CONTENT_TYPE;
+ case MATCH_PREVIEW_PROGRAM_ID:
+ return PreviewPrograms.CONTENT_ITEM_TYPE;
+ case MATCH_WATCH_NEXT_PROGRAM:
+ return WatchNextPrograms.CONTENT_TYPE;
+ case MATCH_WATCH_NEXT_PROGRAM_ID:
+ return WatchNextPrograms.CONTENT_ITEM_TYPE;
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ ensureInitialized();
+ boolean needsToValidateSortOrder = !callerHasAccessAllEpgDataPermission();
+ SqlParams params = createSqlParams(OP_QUERY, uri, selection, selectionArgs);
+
+ SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+ queryBuilder.setStrict(needsToValidateSortOrder);
+ queryBuilder.setTables(params.getTables());
+ String orderBy = null;
+ Map<String, String> projectionMap;
+ switch (params.getTables()) {
+ case PROGRAMS_TABLE:
+ projectionMap = sProgramProjectionMap;
+ orderBy = DEFAULT_PROGRAMS_SORT_ORDER;
+ break;
+ case WATCHED_PROGRAMS_TABLE:
+ projectionMap = sWatchedProgramProjectionMap;
+ orderBy = DEFAULT_WATCHED_PROGRAMS_SORT_ORDER;
+ break;
+ case RECORDED_PROGRAMS_TABLE:
+ projectionMap = sRecordedProgramProjectionMap;
+ break;
+ case PREVIEW_PROGRAMS_TABLE:
+ projectionMap = sPreviewProgramProjectionMap;
+ break;
+ case WATCH_NEXT_PROGRAMS_TABLE:
+ projectionMap = sWatchNextProgramProjectionMap;
+ break;
+ default:
+ projectionMap = sChannelProjectionMap;
+ break;
+ }
+ queryBuilder.setProjectionMap(createProjectionMapForQuery(projection, projectionMap));
+ if (needsToValidateSortOrder) {
+ validateSortOrder(sortOrder, projectionMap.keySet());
+ }
+
+ // Use the default sort order only if no sort order is specified.
+ if (!TextUtils.isEmpty(sortOrder)) {
+ orderBy = sortOrder;
+ }
+
+ // Get the database and run the query.
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor c =
+ queryBuilder.query(
+ db,
+ projection,
+ params.getSelection(),
+ params.getSelectionArgs(),
+ null,
+ null,
+ orderBy);
+
+ // Tell the cursor what URI to watch, so it knows when its source data changes.
+ c.setNotificationUri(getContext().getContentResolver(), uri);
+ return c;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ ensureInitialized();
+ switch (sUriMatcher.match(uri)) {
+ case MATCH_CHANNEL:
+ // Preview channels are not necessarily associated with TV input service.
+ // Therefore, we fill a fake ID to meet not null restriction for preview channels.
+ if (values.get(Channels.COLUMN_INPUT_ID) == null
+ && TvContractCompat.PARAM_CHANNEL.equals(
+ values.get(Channels.COLUMN_TYPE))) {
+ values.put(Channels.COLUMN_INPUT_ID, EMPTY_STRING);
+ }
+ filterContentValues(values, sChannelProjectionMap);
+ return insertChannel(uri, values);
+ case MATCH_PROGRAM:
+ filterContentValues(values, sProgramProjectionMap);
+ return insertProgram(uri, values);
+ case MATCH_WATCHED_PROGRAM:
+ return insertWatchedProgram(uri, values);
+ case MATCH_RECORDED_PROGRAM:
+ filterContentValues(values, sRecordedProgramProjectionMap);
+ return insertRecordedProgram(uri, values);
+ case MATCH_PREVIEW_PROGRAM:
+ filterContentValues(values, sPreviewProgramProjectionMap);
+ return insertPreviewProgram(uri, values);
+ case MATCH_WATCH_NEXT_PROGRAM:
+ filterContentValues(values, sWatchNextProgramProjectionMap);
+ return insertWatchNextProgram(uri, values);
+ case MATCH_CHANNEL_ID:
+ case MATCH_CHANNEL_ID_LOGO:
+ case MATCH_PASSTHROUGH_ID:
+ case MATCH_PROGRAM_ID:
+ case MATCH_WATCHED_PROGRAM_ID:
+ case MATCH_RECORDED_PROGRAM_ID:
+ case MATCH_PREVIEW_PROGRAM_ID:
+ throw new UnsupportedOperationException("Cannot insert into that URI: " + uri);
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ }
+
+ private Uri insertChannel(Uri uri, ContentValues values) {
+ if (TextUtils.equals(
+ values.getAsString(Channels.COLUMN_TYPE), TvContractCompat.Channels.TYPE_PREVIEW)) {
+ blockIllegalAccessFromBlockedPackage();
+ }
+ // Mark the owner package of this channel.
+ values.put(Channels.COLUMN_PACKAGE_NAME, getCallingPackage_());
+ blockIllegalAccessToChannelsSystemColumns(values);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(CHANNELS_TABLE, null, values);
+ if (rowId > 0) {
+ Uri channelUri = TvContractCompat.buildChannelUri(rowId);
+ notifyChange(channelUri);
+ return channelUri;
+ }
+
+ throw new SQLException("Failed to insert row into " + uri);
+ }
+
+ private Uri insertProgram(Uri uri, ContentValues values) {
+ if (!callerHasAccessAllEpgDataPermission()
+ || !values.containsKey(Programs.COLUMN_PACKAGE_NAME)) {
+ // Mark the owner package of this program. System app with a proper permission may
+ // change the owner of the program.
+ values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_());
+ }
+
+ checkAndConvertGenre(values);
+ checkAndConvertDeprecatedColumns(values);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(PROGRAMS_TABLE, null, values);
+ if (rowId > 0) {
+ Uri programUri = TvContractCompat.buildProgramUri(rowId);
+ notifyChange(programUri);
+ return programUri;
+ }
+
+ throw new SQLException("Failed to insert row into " + uri);
+ }
+
+ private Uri insertWatchedProgram(Uri uri, ContentValues values) {
+ if (DEBUG) {
+ Log.d(TAG, "insertWatchedProgram(uri=" + uri + ", values={" + values + "})");
+ }
+ Long watchStartTime = values.getAsLong(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS);
+ Long watchEndTime = values.getAsLong(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS);
+ // The system sends only two kinds of watch events:
+ // 1. The user tunes to a new channel. (COLUMN_WATCH_START_TIME_UTC_MILLIS)
+ // 2. The user stops watching. (COLUMN_WATCH_END_TIME_UTC_MILLIS)
+ if (watchStartTime != null && watchEndTime == null) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(WATCHED_PROGRAMS_TABLE, null, values);
+ if (rowId > 0) {
+ return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, rowId);
+ }
+ Log.w(TAG, "Failed to insert row for " + values + ". Channel does not exist.");
+ return null;
+ } else if (watchStartTime == null && watchEndTime != null) {
+ return null;
+ }
+ // All the other cases are invalid.
+ throw new IllegalArgumentException(
+ "Only one of COLUMN_WATCH_START_TIME_UTC_MILLIS and"
+ + " COLUMN_WATCH_END_TIME_UTC_MILLIS should be specified");
+ }
+
+ private Uri insertRecordedProgram(Uri uri, ContentValues values) {
+ // Mark the owner package of this program.
+ values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_());
+
+ checkAndConvertGenre(values);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(RECORDED_PROGRAMS_TABLE, null, values);
+ if (rowId > 0) {
+ Uri recordedProgramUri = TvContractCompat.buildRecordedProgramUri(rowId);
+ notifyChange(recordedProgramUri);
+ return recordedProgramUri;
+ }
+
+ throw new SQLException("Failed to insert row into " + uri);
+ }
+
+ private Uri insertPreviewProgram(Uri uri, ContentValues values) {
+ if (!values.containsKey(PreviewPrograms.COLUMN_TYPE)) {
+ throw new IllegalArgumentException(
+ "Missing the required column: " + PreviewPrograms.COLUMN_TYPE);
+ }
+ blockIllegalAccessFromBlockedPackage();
+ // Mark the owner package of this program.
+ values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_());
+ blockIllegalAccessToPreviewProgramsSystemColumns(values);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(PREVIEW_PROGRAMS_TABLE, null, values);
+ if (rowId > 0) {
+ Uri previewProgramUri = TvContractCompat.buildPreviewProgramUri(rowId);
+ notifyChange(previewProgramUri);
+ return previewProgramUri;
+ }
+
+ throw new SQLException("Failed to insert row into " + uri);
+ }
+
+ private Uri insertWatchNextProgram(Uri uri, ContentValues values) {
+ if (!values.containsKey(WatchNextPrograms.COLUMN_TYPE)) {
+ throw new IllegalArgumentException(
+ "Missing the required column: " + WatchNextPrograms.COLUMN_TYPE);
+ }
+ blockIllegalAccessFromBlockedPackage();
+ if (!callerHasAccessAllEpgDataPermission()
+ || !values.containsKey(Programs.COLUMN_PACKAGE_NAME)) {
+ // Mark the owner package of this program. System app with a proper permission may
+ // change the owner of the program.
+ values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_());
+ }
+ blockIllegalAccessToPreviewProgramsSystemColumns(values);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(WATCH_NEXT_PROGRAMS_TABLE, null, values);
+ if (rowId > 0) {
+ Uri watchNextProgramUri = TvContractCompat.buildWatchNextProgramUri(rowId);
+ notifyChange(watchNextProgramUri);
+ return watchNextProgramUri;
+ }
+
+ throw new SQLException("Failed to insert row into " + uri);
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ SqlParams params = createSqlParams(OP_DELETE, uri, selection, selectionArgs);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int count;
+ switch (sUriMatcher.match(uri)) {
+ case MATCH_CHANNEL_ID_LOGO:
+ ContentValues values = new ContentValues();
+ values.putNull(CHANNELS_COLUMN_LOGO);
+ count =
+ db.update(
+ params.getTables(),
+ values,
+ params.getSelection(),
+ params.getSelectionArgs());
+ break;
+ case MATCH_CHANNEL:
+ case MATCH_PROGRAM:
+ case MATCH_WATCHED_PROGRAM:
+ case MATCH_RECORDED_PROGRAM:
+ case MATCH_PREVIEW_PROGRAM:
+ case MATCH_WATCH_NEXT_PROGRAM:
+ case MATCH_CHANNEL_ID:
+ case MATCH_PASSTHROUGH_ID:
+ case MATCH_PROGRAM_ID:
+ case MATCH_WATCHED_PROGRAM_ID:
+ case MATCH_RECORDED_PROGRAM_ID:
+ case MATCH_PREVIEW_PROGRAM_ID:
+ case MATCH_WATCH_NEXT_PROGRAM_ID:
+ count =
+ db.delete(
+ params.getTables(),
+ params.getSelection(),
+ params.getSelectionArgs());
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ if (count > 0) {
+ notifyChange(uri);
+ }
+ return count;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ SqlParams params = createSqlParams(OP_UPDATE, uri, selection, selectionArgs);
+ blockIllegalAccessToIdAndPackageName(uri, values);
+ boolean containImmutableColumn = false;
+ if (params.getTables().equals(CHANNELS_TABLE)) {
+ filterContentValues(values, sChannelProjectionMap);
+ containImmutableColumn = disallowModifyChannelType(values, params);
+ if (containImmutableColumn && sUriMatcher.match(uri) != MATCH_CHANNEL_ID) {
+ Log.i(TAG, "Updating failed. Attempt to change immutable column for channels.");
+ return 0;
+ }
+ blockIllegalAccessToChannelsSystemColumns(values);
+ } else if (params.getTables().equals(PROGRAMS_TABLE)) {
+ filterContentValues(values, sProgramProjectionMap);
+ checkAndConvertGenre(values);
+ checkAndConvertDeprecatedColumns(values);
+ } else if (params.getTables().equals(RECORDED_PROGRAMS_TABLE)) {
+ filterContentValues(values, sRecordedProgramProjectionMap);
+ checkAndConvertGenre(values);
+ } else if (params.getTables().equals(PREVIEW_PROGRAMS_TABLE)) {
+ filterContentValues(values, sPreviewProgramProjectionMap);
+ containImmutableColumn = disallowModifyChannelId(values, params);
+ if (containImmutableColumn && PreviewPrograms.CONTENT_URI.equals(uri)) {
+ Log.i(
+ TAG,
+ "Updating failed. Attempt to change unmodifiable column for "
+ + "preview programs.");
+ return 0;
+ }
+ blockIllegalAccessToPreviewProgramsSystemColumns(values);
+ } else if (params.getTables().equals(WATCH_NEXT_PROGRAMS_TABLE)) {
+ filterContentValues(values, sWatchNextProgramProjectionMap);
+ blockIllegalAccessToPreviewProgramsSystemColumns(values);
+ }
+ if (values.size() == 0) {
+ // All values may be filtered out, no need to update
+ return 0;
+ }
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int count =
+ db.update(
+ params.getTables(),
+ values,
+ params.getSelection(),
+ params.getSelectionArgs());
+ if (count > 0) {
+ notifyChange(uri);
+ } else if (containImmutableColumn) {
+ Log.i(
+ TAG,
+ "Updating failed. The item may not exist or attempt to change "
+ + "immutable column.");
+ }
+ return count;
+ }
+
+ private synchronized void ensureInitialized() {
+ if (!sInitialized) {
+ // Database is not accessed before and the projection maps and the blocked package list
+ // are not updated yet. Gets database here to make it initialized.
+ mOpenHelper.getReadableDatabase();
+ }
+ }
+
+ private static synchronized void initOnOpenIfNeeded(Context context, SQLiteDatabase db) {
+ if (!sInitialized) {
+ updateProjectionMap(db, CHANNELS_TABLE, sChannelProjectionMap);
+ updateProjectionMap(db, PROGRAMS_TABLE, sProgramProjectionMap);
+ updateProjectionMap(db, WATCHED_PROGRAMS_TABLE, sWatchedProgramProjectionMap);
+ updateProjectionMap(db, RECORDED_PROGRAMS_TABLE, sRecordedProgramProjectionMap);
+ updateProjectionMap(db, PREVIEW_PROGRAMS_TABLE, sPreviewProgramProjectionMap);
+ updateProjectionMap(db, WATCH_NEXT_PROGRAMS_TABLE, sWatchNextProgramProjectionMap);
+ sBlockedPackagesSharedPreference =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ sBlockedPackages = new ConcurrentHashMap<>();
+ for (String packageName :
+ sBlockedPackagesSharedPreference.getStringSet(
+ SHARED_PREF_BLOCKED_PACKAGES_KEY, new HashSet<>())) {
+ sBlockedPackages.put(packageName, true);
+ }
+ sInitialized = true;
+ }
+ }
+
+ private static void updateProjectionMap(
+ SQLiteDatabase db, String tableName, Map<String, String> projectionMap) {
+ try (Cursor cursor = db.rawQuery("SELECT * FROM " + tableName + " LIMIT 0", null)) {
+ for (String columnName : cursor.getColumnNames()) {
+ if (!projectionMap.containsKey(columnName)) {
+ projectionMap.put(columnName, tableName + '.' + columnName);
+ }
+ }
+ }
+ }
+
+ private Map<String, String> createProjectionMapForQuery(
+ String[] projection, Map<String, String> projectionMap) {
+ if (projection == null) {
+ return projectionMap;
+ }
+ Map<String, String> columnProjectionMap = new HashMap<>();
+ for (String columnName : projection) {
+ // Value NULL will be provided if the requested column does not exist in the database.
+ columnProjectionMap.put(
+ columnName, projectionMap.getOrDefault(columnName, "NULL as " + columnName));
+ }
+ return columnProjectionMap;
+ }
+
+ private void filterContentValues(ContentValues values, Map<String, String> projectionMap) {
+ Iterator<String> iter = values.keySet().iterator();
+ while (iter.hasNext()) {
+ String columnName = iter.next();
+ if (!projectionMap.containsKey(columnName)) {
+ iter.remove();
+ }
+ }
+ }
+
+ private SqlParams createSqlParams(
+ String operation, Uri uri, String selection, String[] selectionArgs) {
+ int match = sUriMatcher.match(uri);
+ SqlParams params = new SqlParams(null, selection, selectionArgs);
+
+ // Control access to EPG data (excluding watched programs) when the caller doesn't have all
+ // access.
+ String prefix = match == MATCH_CHANNEL ? CHANNELS_TABLE + "." : "";
+ if (!callerHasAccessAllEpgDataPermission()
+ && match != MATCH_WATCHED_PROGRAM
+ && match != MATCH_WATCHED_PROGRAM_ID) {
+ if (!TextUtils.isEmpty(selection)) {
+ throw new SecurityException("Selection not allowed for " + uri);
+ }
+ // Limit the operation only to the data that the calling package owns except for when
+ // the caller tries to read TV listings and has the appropriate permission.
+ if (operation.equals(OP_QUERY) && callerHasReadTvListingsPermission()) {
+ params.setWhere(
+ prefix
+ + BaseTvColumns.COLUMN_PACKAGE_NAME
+ + "=? OR "
+ + Channels.COLUMN_SEARCHABLE
+ + "=?",
+ getCallingPackage_(),
+ "1");
+ } else {
+ params.setWhere(
+ prefix + BaseTvColumns.COLUMN_PACKAGE_NAME + "=?", getCallingPackage_());
+ }
+ }
+ String packageName = uri.getQueryParameter(PARAM_PACKAGE);
+ if (packageName != null) {
+ params.appendWhere(prefix + BaseTvColumns.COLUMN_PACKAGE_NAME + "=?", packageName);
+ }
+
+ switch (match) {
+ case MATCH_CHANNEL:
+ String genre = uri.getQueryParameter(TvContractCompat.PARAM_CANONICAL_GENRE);
+ if (genre == null) {
+ params.setTables(CHANNELS_TABLE);
+ } else {
+ if (!operation.equals(OP_QUERY)) {
+ throw new SecurityException(
+ capitalize(operation) + " not allowed for " + uri);
+ }
+ if (!Genres.isCanonical(genre)) {
+ throw new IllegalArgumentException("Not a canonical genre : " + genre);
+ }
+ params.setTables(CHANNELS_TABLE_INNER_JOIN_PROGRAMS_TABLE);
+ String curTime = String.valueOf(System.currentTimeMillis());
+ params.appendWhere(
+ "LIKE(?, "
+ + Programs.COLUMN_CANONICAL_GENRE
+ + ") AND "
+ + Programs.COLUMN_START_TIME_UTC_MILLIS
+ + "<=? AND "
+ + Programs.COLUMN_END_TIME_UTC_MILLIS
+ + ">=?",
+ "%" + genre + "%",
+ curTime,
+ curTime);
+ }
+ String inputId = uri.getQueryParameter(TvContractCompat.PARAM_INPUT);
+ if (inputId != null) {
+ params.appendWhere(Channels.COLUMN_INPUT_ID + "=?", inputId);
+ }
+ boolean browsableOnly =
+ uri.getBooleanQueryParameter(TvContractCompat.PARAM_BROWSABLE_ONLY, false);
+ if (browsableOnly) {
+ params.appendWhere(Channels.COLUMN_BROWSABLE + "=1");
+ }
+ String preview = uri.getQueryParameter(PARAM_PREVIEW);
+ if (preview != null) {
+ String previewSelection =
+ Channels.COLUMN_TYPE
+ + (preview.equals(String.valueOf(true)) ? "=?" : "!=?");
+ params.appendWhere(previewSelection, Channels.TYPE_PREVIEW);
+ }
+ break;
+ case MATCH_CHANNEL_ID:
+ params.setTables(CHANNELS_TABLE);
+ params.appendWhere(Channels._ID + "=?", uri.getLastPathSegment());
+ break;
+ case MATCH_PROGRAM:
+ params.setTables(PROGRAMS_TABLE);
+ String paramChannelId = uri.getQueryParameter(TvContractCompat.PARAM_CHANNEL);
+ if (paramChannelId != null) {
+ String channelId = String.valueOf(Long.parseLong(paramChannelId));
+ params.appendWhere(Programs.COLUMN_CHANNEL_ID + "=?", channelId);
+ }
+ String paramStartTime = uri.getQueryParameter(TvContractCompat.PARAM_START_TIME);
+ String paramEndTime = uri.getQueryParameter(TvContractCompat.PARAM_END_TIME);
+ if (paramStartTime != null && paramEndTime != null) {
+ String startTime = String.valueOf(Long.parseLong(paramStartTime));
+ String endTime = String.valueOf(Long.parseLong(paramEndTime));
+ params.appendWhere(
+ Programs.COLUMN_START_TIME_UTC_MILLIS
+ + "<=? AND "
+ + Programs.COLUMN_END_TIME_UTC_MILLIS
+ + ">=? AND ?<=?",
+ endTime,
+ startTime,
+ startTime,
+ endTime);
+ }
+ break;
+ case MATCH_PROGRAM_ID:
+ params.setTables(PROGRAMS_TABLE);
+ params.appendWhere(Programs._ID + "=?", uri.getLastPathSegment());
+ break;
+ case MATCH_WATCHED_PROGRAM:
+ if (!callerHasAccessWatchedProgramsPermission()) {
+ throw new SecurityException("Access not allowed for " + uri);
+ }
+ params.setTables(WATCHED_PROGRAMS_TABLE);
+ params.appendWhere(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=?", "1");
+ break;
+ case MATCH_WATCHED_PROGRAM_ID:
+ if (!callerHasAccessWatchedProgramsPermission()) {
+ throw new SecurityException("Access not allowed for " + uri);
+ }
+ params.setTables(WATCHED_PROGRAMS_TABLE);
+ params.appendWhere(WatchedPrograms._ID + "=?", uri.getLastPathSegment());
+ params.appendWhere(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=?", "1");
+ break;
+ case MATCH_RECORDED_PROGRAM_ID:
+ params.appendWhere(RecordedPrograms._ID + "=?", uri.getLastPathSegment());
+ // fall-through
+ case MATCH_RECORDED_PROGRAM:
+ params.setTables(RECORDED_PROGRAMS_TABLE);
+ paramChannelId = uri.getQueryParameter(TvContractCompat.PARAM_CHANNEL);
+ if (paramChannelId != null) {
+ String channelId = String.valueOf(Long.parseLong(paramChannelId));
+ params.appendWhere(Programs.COLUMN_CHANNEL_ID + "=?", channelId);
+ }
+ break;
+ case MATCH_PREVIEW_PROGRAM_ID:
+ params.appendWhere(PreviewPrograms._ID + "=?", uri.getLastPathSegment());
+ // fall-through
+ case MATCH_PREVIEW_PROGRAM:
+ params.setTables(PREVIEW_PROGRAMS_TABLE);
+ paramChannelId = uri.getQueryParameter(TvContractCompat.PARAM_CHANNEL);
+ if (paramChannelId != null) {
+ String channelId = String.valueOf(Long.parseLong(paramChannelId));
+ params.appendWhere(PreviewPrograms.COLUMN_CHANNEL_ID + "=?", channelId);
+ }
+ break;
+ case MATCH_WATCH_NEXT_PROGRAM_ID:
+ params.appendWhere(WatchNextPrograms._ID + "=?", uri.getLastPathSegment());
+ // fall-through
+ case MATCH_WATCH_NEXT_PROGRAM:
+ params.setTables(WATCH_NEXT_PROGRAMS_TABLE);
+ break;
+ case MATCH_CHANNEL_ID_LOGO:
+ if (operation.equals(OP_DELETE)) {
+ params.setTables(CHANNELS_TABLE);
+ params.appendWhere(Channels._ID + "=?", uri.getPathSegments().get(1));
+ break;
+ }
+ // fall-through
+ case MATCH_PASSTHROUGH_ID:
+ throw new UnsupportedOperationException(operation + " not permmitted on " + uri);
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ return params;
+ }
+
+ private static String capitalize(String str) {
+ return Character.toUpperCase(str.charAt(0)) + str.substring(1);
+ }
+
+ @SuppressLint("DefaultLocale")
+ private void checkAndConvertGenre(ContentValues values) {
+ String canonicalGenres = values.getAsString(Programs.COLUMN_CANONICAL_GENRE);
+
+ if (!TextUtils.isEmpty(canonicalGenres)) {
+ // Check if the canonical genres are valid. If not, clear them.
+ String[] genres = Genres.decode(canonicalGenres);
+ for (String genre : genres) {
+ if (!Genres.isCanonical(genre)) {
+ values.putNull(Programs.COLUMN_CANONICAL_GENRE);
+ canonicalGenres = null;
+ break;
+ }
+ }
+ }
+
+ if (TextUtils.isEmpty(canonicalGenres)) {
+ // If the canonical genre is not set, try to map the broadcast genre to the canonical
+ // genre.
+ String broadcastGenres = values.getAsString(Programs.COLUMN_BROADCAST_GENRE);
+ if (!TextUtils.isEmpty(broadcastGenres)) {
+ Set<String> genreSet = new HashSet<>();
+ String[] genres = Genres.decode(broadcastGenres);
+ for (String genre : genres) {
+ String canonicalGenre = sGenreMap.get(genre.toUpperCase());
+ if (Genres.isCanonical(canonicalGenre)) {
+ genreSet.add(canonicalGenre);
+ }
+ }
+ if (genreSet.size() > 0) {
+ values.put(
+ Programs.COLUMN_CANONICAL_GENRE,
+ Genres.encode(genreSet.toArray(new String[genreSet.size()])));
+ }
+ }
+ }
+ }
+
+ private void checkAndConvertDeprecatedColumns(ContentValues values) {
+ if (values.containsKey(Programs.COLUMN_SEASON_NUMBER)) {
+ if (!values.containsKey(Programs.COLUMN_SEASON_DISPLAY_NUMBER)) {
+ values.put(
+ Programs.COLUMN_SEASON_DISPLAY_NUMBER,
+ values.getAsInteger(Programs.COLUMN_SEASON_NUMBER));
+ }
+ values.remove(Programs.COLUMN_SEASON_NUMBER);
+ }
+ if (values.containsKey(Programs.COLUMN_EPISODE_NUMBER)) {
+ if (!values.containsKey(Programs.COLUMN_EPISODE_DISPLAY_NUMBER)) {
+ values.put(
+ Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
+ values.getAsInteger(Programs.COLUMN_EPISODE_NUMBER));
+ }
+ values.remove(Programs.COLUMN_EPISODE_NUMBER);
+ }
+ }
+
+ // We might have more than one thread trying to make its way through applyBatch() so the
+ // notification coalescing needs to be thread-local to work correctly.
+ private final ThreadLocal<Set<Uri>> mTLBatchNotifications = new ThreadLocal<>();
+
+ private Set<Uri> getBatchNotificationsSet() {
+ return mTLBatchNotifications.get();
+ }
+
+ private void setBatchNotificationsSet(Set<Uri> batchNotifications) {
+ mTLBatchNotifications.set(batchNotifications);
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ setBatchNotificationsSet(new HashSet<Uri>());
+ Context context = getContext();
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ ContentProviderResult[] results = super.applyBatch(operations);
+ db.setTransactionSuccessful();
+ return results;
+ } finally {
+ db.endTransaction();
+ final Set<Uri> notifications = getBatchNotificationsSet();
+ setBatchNotificationsSet(null);
+ for (final Uri uri : notifications) {
+ context.getContentResolver().notifyChange(uri, null);
+ }
+ }
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ setBatchNotificationsSet(new HashSet<Uri>());
+ Context context = getContext();
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ int result = super.bulkInsert(uri, values);
+ db.setTransactionSuccessful();
+ return result;
+ } finally {
+ db.endTransaction();
+ final Set<Uri> notifications = getBatchNotificationsSet();
+ setBatchNotificationsSet(null);
+ for (final Uri notificationUri : notifications) {
+ context.getContentResolver().notifyChange(notificationUri, null);
+ }
+ }
+ }
+
+ private void notifyChange(Uri uri) {
+ final Set<Uri> batchNotifications = getBatchNotificationsSet();
+ if (batchNotifications != null) {
+ batchNotifications.add(uri);
+ } else {
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+ }
+
+ private boolean callerHasReadTvListingsPermission() {
+ return getContext().checkCallingOrSelfPermission(PERMISSION_READ_TV_LISTINGS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean callerHasAccessAllEpgDataPermission() {
+ return getContext().checkCallingOrSelfPermission(PERMISSION_ACCESS_ALL_EPG_DATA)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean callerHasAccessWatchedProgramsPermission() {
+ return getContext().checkCallingOrSelfPermission(PERMISSION_ACCESS_WATCHED_PROGRAMS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean callerHasModifyParentalControlsPermission() {
+ return getContext()
+ .checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void blockIllegalAccessToIdAndPackageName(Uri uri, ContentValues values) {
+ if (values.containsKey(BaseColumns._ID)) {
+ int match = sUriMatcher.match(uri);
+ switch (match) {
+ case MATCH_CHANNEL_ID:
+ case MATCH_PROGRAM_ID:
+ case MATCH_PREVIEW_PROGRAM_ID:
+ case MATCH_RECORDED_PROGRAM_ID:
+ case MATCH_WATCH_NEXT_PROGRAM_ID:
+ case MATCH_WATCHED_PROGRAM_ID:
+ if (TextUtils.equals(
+ values.getAsString(BaseColumns._ID), uri.getLastPathSegment())) {
+ break;
+ }
+ // fall through
+ default:
+ throw new IllegalArgumentException("Not allowed to change ID.");
+ }
+ }
+ if (values.containsKey(BaseTvColumns.COLUMN_PACKAGE_NAME)
+ && !callerHasAccessAllEpgDataPermission()
+ && !TextUtils.equals(
+ values.getAsString(BaseTvColumns.COLUMN_PACKAGE_NAME),
+ getCallingPackage_())) {
+ throw new SecurityException("Not allowed to change package name.");
+ }
+ }
+
+ private void blockIllegalAccessToChannelsSystemColumns(ContentValues values) {
+ if (values.containsKey(Channels.COLUMN_LOCKED)
+ && !callerHasModifyParentalControlsPermission()) {
+ throw new SecurityException("Not allowed to access Channels.COLUMN_LOCKED");
+ }
+ Boolean hasAccessAllEpgDataPermission = null;
+ if (values.containsKey(Channels.COLUMN_BROWSABLE)) {
+ hasAccessAllEpgDataPermission = callerHasAccessAllEpgDataPermission();
+ if (!hasAccessAllEpgDataPermission) {
+ throw new SecurityException("Not allowed to access Channels.COLUMN_BROWSABLE");
+ }
+ }
+ }
+
+ private void blockIllegalAccessToPreviewProgramsSystemColumns(ContentValues values) {
+ if (values.containsKey(PreviewPrograms.COLUMN_BROWSABLE)
+ && !callerHasAccessAllEpgDataPermission()) {
+ throw new SecurityException("Not allowed to access Programs.COLUMN_BROWSABLE");
+ }
+ }
+
+ private void blockIllegalAccessFromBlockedPackage() {
+ String callingPackageName = getCallingPackage_();
+ if (sBlockedPackages.containsKey(callingPackageName)) {
+ throw new SecurityException(
+ "Not allowed to access "
+ + TvContractCompat.AUTHORITY
+ + ", "
+ + callingPackageName
+ + " is blocked");
+ }
+ }
+
+ private boolean disallowModifyChannelType(ContentValues values, SqlParams params) {
+ if (values.containsKey(Channels.COLUMN_TYPE)) {
+ params.appendWhere(
+ Channels.COLUMN_TYPE + "=?", values.getAsString(Channels.COLUMN_TYPE));
+ return true;
+ }
+ return false;
+ }
+
+ private boolean disallowModifyChannelId(ContentValues values, SqlParams params) {
+ if (values.containsKey(PreviewPrograms.COLUMN_CHANNEL_ID)) {
+ params.appendWhere(
+ PreviewPrograms.COLUMN_CHANNEL_ID + "=?",
+ values.getAsString(PreviewPrograms.COLUMN_CHANNEL_ID));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ switch (sUriMatcher.match(uri)) {
+ case MATCH_CHANNEL_ID_LOGO:
+ return openLogoFile(uri, mode);
+ default:
+ throw new FileNotFoundException(uri.toString());
+ }
+ }
+
+ private ParcelFileDescriptor openLogoFile(Uri uri, String mode) throws FileNotFoundException {
+ long channelId = Long.parseLong(uri.getPathSegments().get(1));
+
+ SqlParams params =
+ new SqlParams(CHANNELS_TABLE, Channels._ID + "=?", String.valueOf(channelId));
+ if (!callerHasAccessAllEpgDataPermission()) {
+ if (callerHasReadTvListingsPermission()) {
+ params.appendWhere(
+ Channels.COLUMN_PACKAGE_NAME + "=? OR " + Channels.COLUMN_SEARCHABLE + "=?",
+ getCallingPackage_(),
+ "1");
+ } else {
+ params.appendWhere(Channels.COLUMN_PACKAGE_NAME + "=?", getCallingPackage_());
+ }
+ }
+
+ SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+ queryBuilder.setTables(params.getTables());
+
+ // We don't write the database here.
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ if (mode.equals("r")) {
+ String sql =
+ queryBuilder.buildQuery(
+ new String[] {CHANNELS_COLUMN_LOGO},
+ params.getSelection(),
+ null,
+ null,
+ null,
+ null);
+ ParcelFileDescriptor fd =
+ DatabaseUtils.blobFileDescriptorForQuery(db, sql, params.getSelectionArgs());
+ if (fd == null) {
+ throw new FileNotFoundException(uri.toString());
+ }
+ return fd;
+ } else {
+ try (Cursor cursor =
+ queryBuilder.query(
+ db,
+ new String[] {Channels._ID},
+ params.getSelection(),
+ params.getSelectionArgs(),
+ null,
+ null,
+ null)) {
+ if (cursor.getCount() < 1) {
+ // Fails early if corresponding channel does not exist.
+ // PipeMonitor may still fail to update DB later.
+ throw new FileNotFoundException(uri.toString());
+ }
+ }
+
+ try {
+ ParcelFileDescriptor[] pipeFds = ParcelFileDescriptor.createPipe();
+ PipeMonitor pipeMonitor = new PipeMonitor(pipeFds[0], channelId, params);
+ pipeMonitor.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ return pipeFds[1];
+ } catch (IOException ioe) {
+ FileNotFoundException fne = new FileNotFoundException(uri.toString());
+ fne.initCause(ioe);
+ throw fne;
+ }
+ }
+ }
+
+ /**
+ * Validates the sort order based on the given field set.
+ *
+ * @throws IllegalArgumentException if there is any unknown field.
+ */
+ @SuppressLint("DefaultLocale")
+ private static void validateSortOrder(String sortOrder, Set<String> possibleFields) {
+ if (TextUtils.isEmpty(sortOrder) || possibleFields.isEmpty()) {
+ return;
+ }
+ String[] orders = sortOrder.split(",");
+ for (String order : orders) {
+ String field =
+ order.replaceAll("\\s+", " ")
+ .trim()
+ .toLowerCase()
+ .replace(" asc", "")
+ .replace(" desc", "");
+ if (!possibleFields.contains(field)) {
+ throw new IllegalArgumentException("Illegal field in sort order " + order);
+ }
+ }
+ }
+
+ private class PipeMonitor extends AsyncTask<Void, Void, Void> {
+ private final ParcelFileDescriptor mPfd;
+ private final long mChannelId;
+ private final SqlParams mParams;
+
+ private PipeMonitor(ParcelFileDescriptor pfd, long channelId, SqlParams params) {
+ mPfd = pfd;
+ mChannelId = channelId;
+ mParams = params;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ int count = 0;
+ try (AutoCloseInputStream is = new AutoCloseInputStream(mPfd);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ Bitmap bitmap = BitmapFactory.decodeStream(is);
+ if (bitmap == null) {
+ Log.e(TAG, "Failed to decode logo image for channel ID " + mChannelId);
+ return null;
+ }
+
+ float scaleFactor =
+ Math.min(
+ 1f,
+ ((float) MAX_LOGO_IMAGE_SIZE)
+ / Math.max(bitmap.getWidth(), bitmap.getHeight()));
+ if (scaleFactor < 1f) {
+ bitmap =
+ Bitmap.createScaledBitmap(
+ bitmap,
+ (int) (bitmap.getWidth() * scaleFactor),
+ (int) (bitmap.getHeight() * scaleFactor),
+ false);
+ }
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+ byte[] bytes = baos.toByteArray();
+
+ ContentValues values = new ContentValues();
+ values.put(CHANNELS_COLUMN_LOGO, bytes);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ count =
+ db.update(
+ mParams.getTables(),
+ values,
+ mParams.getSelection(),
+ mParams.getSelectionArgs());
+ if (count > 0) {
+ Uri uri = TvContractCompat.buildChannelLogoUri(mChannelId);
+ notifyChange(uri);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write logo for channel ID " + mChannelId, e);
+
+ } finally {
+ if (count == 0) {
+ try {
+ mPfd.closeWithError("Failed to write logo for channel ID " + mChannelId);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Failed to close pipe", ioe);
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Column definitions for the TV programs that the user watched. Applications do not have access
+ * to this table.
+ *
+ * <p>
+ *
+ * <p>By default, the query results will be sorted by {@link
+ * WatchedPrograms#COLUMN_WATCH_START_TIME_UTC_MILLIS} in descending order.
+ *
+ * @hide
+ */
+ public static final class WatchedPrograms implements BaseTvColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + TvContract.AUTHORITY + "/watched_program");
+
+ /** The MIME type of a directory of watched programs. */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/watched_program";
+
+ /** The MIME type of a single item in this table. */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/watched_program";
+
+ /**
+ * The UTC time that the user started watching this TV program, in milliseconds since the
+ * epoch.
+ *
+ * <p>
+ *
+ * <p>Type: INTEGER (long)
+ */
+ public static final String COLUMN_WATCH_START_TIME_UTC_MILLIS =
+ "watch_start_time_utc_millis";
+
+ /**
+ * The UTC time that the user stopped watching this TV program, in milliseconds since the
+ * epoch.
+ *
+ * <p>
+ *
+ * <p>Type: INTEGER (long)
+ */
+ public static final String COLUMN_WATCH_END_TIME_UTC_MILLIS = "watch_end_time_utc_millis";
+
+ /**
+ * The ID of the TV channel that provides this TV program.
+ *
+ * <p>
+ *
+ * <p>This is a required field.
+ *
+ * <p>
+ *
+ * <p>Type: INTEGER (long)
+ */
+ public static final String COLUMN_CHANNEL_ID = "channel_id";
+
+ /**
+ * The title of this TV program.
+ *
+ * <p>
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_TITLE = "title";
+
+ /**
+ * The start time of this TV program, in milliseconds since the epoch.
+ *
+ * <p>
+ *
+ * <p>Type: INTEGER (long)
+ */
+ public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
+
+ /**
+ * The end time of this TV program, in milliseconds since the epoch.
+ *
+ * <p>
+ *
+ * <p>Type: INTEGER (long)
+ */
+ public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
+
+ /**
+ * The description of this TV program.
+ *
+ * <p>
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_DESCRIPTION = "description";
+
+ /**
+ * Extra parameters given to {@link TvInputService.Session#tune(Uri, android.os.Bundle)
+ * TvInputService.Session.tune(Uri, android.os.Bundle)} when tuning to the channel that
+ * provides this TV program. (Used internally.)
+ *
+ * <p>
+ *
+ * <p>This column contains an encoded string that represents comma-separated key-value pairs
+ * of the tune parameters. (Ex. "[key1]=[value1], [key2]=[value2]"). '%' is used as an
+ * escape character for '%', '=', and ','.
+ *
+ * <p>
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_INTERNAL_TUNE_PARAMS = "tune_params";
+
+ /**
+ * The session token of this TV program. (Used internally.)
+ *
+ * <p>
+ *
+ * <p>This contains a String representation of {@link IBinder} for {@link
+ * TvInputService.Session} that provides the current TV program. It is used internally to
+ * distinguish watched programs entries from different TV input sessions.
+ *
+ * <p>
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_INTERNAL_SESSION_TOKEN = "session_token";
+
+ private WatchedPrograms() {}
+ }
+}