| /* |
| * 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.text.TextUtils; |
| import android.util.Log; |
| import androidx.tvprovider.media.tv.TvContractCompat; |
| import androidx.tvprovider.media.tv.TvContractCompat.BaseTvColumns; |
| import androidx.tvprovider.media.tv.TvContractCompat.Channels; |
| import androidx.tvprovider.media.tv.TvContractCompat.PreviewPrograms; |
| import androidx.tvprovider.media.tv.TvContractCompat.Programs; |
| import androidx.tvprovider.media.tv.TvContractCompat.Programs.Genres; |
| import androidx.tvprovider.media.tv.TvContractCompat.RecordedPrograms; |
| import androidx.tvprovider.media.tv.TvContractCompat.WatchNextPrograms; |
| 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() {} |
| } |
| } |