1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.tv; 18 19 import android.annotation.SuppressLint; 20 import android.app.AlarmManager; 21 import android.app.PendingIntent; 22 import android.content.ContentProvider; 23 import android.content.ContentProviderOperation; 24 import android.content.ContentProviderResult; 25 import android.content.ContentValues; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.OperationApplicationException; 29 import android.content.SharedPreferences; 30 import android.content.UriMatcher; 31 import android.content.pm.PackageManager; 32 import android.database.Cursor; 33 import android.database.DatabaseUtils; 34 import android.database.SQLException; 35 import android.database.sqlite.SQLiteDatabase; 36 import android.database.sqlite.SQLiteOpenHelper; 37 import android.database.sqlite.SQLiteQueryBuilder; 38 import android.graphics.Bitmap; 39 import android.graphics.BitmapFactory; 40 import android.media.tv.TvContract; 41 import android.media.tv.TvContract.BaseTvColumns; 42 import android.media.tv.TvContract.Channels; 43 import android.media.tv.TvContract.PreviewPrograms; 44 import android.media.tv.TvContract.Programs; 45 import android.media.tv.TvContract.Programs.Genres; 46 import android.media.tv.TvContract.RecordedPrograms; 47 import android.media.tv.TvContract.WatchedPrograms; 48 import android.media.tv.TvContract.WatchNextPrograms; 49 import android.net.Uri; 50 import android.os.AsyncTask; 51 import android.os.Bundle; 52 import android.os.Handler; 53 import android.os.Message; 54 import android.os.ParcelFileDescriptor; 55 import android.os.ParcelFileDescriptor.AutoCloseInputStream; 56 import android.preference.PreferenceManager; 57 import android.provider.BaseColumns; 58 import android.text.TextUtils; 59 import android.text.format.DateUtils; 60 import android.util.Log; 61 62 import com.android.internal.annotations.VisibleForTesting; 63 import com.android.internal.os.SomeArgs; 64 import com.android.providers.tv.util.SqlParams; 65 66 import com.android.providers.tv.util.SqliteTokenFinder; 67 import java.util.Locale; 68 import libcore.io.IoUtils; 69 70 import java.io.ByteArrayOutputStream; 71 import java.io.FileNotFoundException; 72 import java.io.IOException; 73 import java.util.ArrayList; 74 import java.util.HashMap; 75 import java.util.HashSet; 76 import java.util.Iterator; 77 import java.util.Map; 78 import java.util.Set; 79 import java.util.concurrent.ConcurrentHashMap; 80 81 /** 82 * TV content provider. The contract between this provider and applications is defined in 83 * {@link android.media.tv.TvContract}. 84 */ 85 public class TvProvider extends ContentProvider { 86 private static final boolean DEBUG = false; 87 private static final String TAG = "TvProvider"; 88 89 static final int DATABASE_VERSION = 34; 90 static final String SHARED_PREF_BLOCKED_PACKAGES_KEY = "blocked_packages"; 91 static final String CHANNELS_TABLE = "channels"; 92 static final String PROGRAMS_TABLE = "programs"; 93 static final String RECORDED_PROGRAMS_TABLE = "recorded_programs"; 94 static final String PREVIEW_PROGRAMS_TABLE = "preview_programs"; 95 static final String WATCH_NEXT_PROGRAMS_TABLE = "watch_next_programs"; 96 static final String WATCHED_PROGRAMS_TABLE = "watched_programs"; 97 static final String PROGRAMS_TABLE_PACKAGE_NAME_INDEX = "programs_package_name_index"; 98 static final String PROGRAMS_TABLE_CHANNEL_ID_INDEX = "programs_channel_id_index"; 99 static final String PROGRAMS_TABLE_START_TIME_INDEX = "programs_start_time_index"; 100 static final String PROGRAMS_TABLE_END_TIME_INDEX = "programs_end_time_index"; 101 static final String WATCHED_PROGRAMS_TABLE_CHANNEL_ID_INDEX = 102 "watched_programs_channel_id_index"; 103 // The internal column in the watched programs table to indicate whether the current log entry 104 // is consolidated or not. Unconsolidated entries may have columns with missing data. 105 static final String WATCHED_PROGRAMS_COLUMN_CONSOLIDATED = "consolidated"; 106 static final String CHANNELS_COLUMN_LOGO = "logo"; 107 private static final String DATABASE_NAME = "tv.db"; 108 private static final String DELETED_CHANNELS_TABLE = "deleted_channels"; // Deprecated 109 private static final String DEFAULT_PROGRAMS_SORT_ORDER = Programs.COLUMN_START_TIME_UTC_MILLIS 110 + " ASC"; 111 private static final String DEFAULT_WATCHED_PROGRAMS_SORT_ORDER = 112 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + " DESC"; 113 private static final String CHANNELS_TABLE_INNER_JOIN_PROGRAMS_TABLE = CHANNELS_TABLE 114 + " INNER JOIN " + PROGRAMS_TABLE 115 + " ON (" + CHANNELS_TABLE + "." + Channels._ID + "=" 116 + PROGRAMS_TABLE + "." + Programs.COLUMN_CHANNEL_ID + ")"; 117 118 // Operation names for createSqlParams(). 119 private static final String OP_QUERY = "query"; 120 private static final String OP_UPDATE = "update"; 121 private static final String OP_DELETE = "delete"; 122 123 private static final UriMatcher sUriMatcher; 124 private static final int MATCH_CHANNEL = 1; 125 private static final int MATCH_CHANNEL_ID = 2; 126 private static final int MATCH_CHANNEL_ID_LOGO = 3; 127 private static final int MATCH_PASSTHROUGH_ID = 4; 128 private static final int MATCH_PROGRAM = 5; 129 private static final int MATCH_PROGRAM_ID = 6; 130 private static final int MATCH_WATCHED_PROGRAM = 7; 131 private static final int MATCH_WATCHED_PROGRAM_ID = 8; 132 private static final int MATCH_RECORDED_PROGRAM = 9; 133 private static final int MATCH_RECORDED_PROGRAM_ID = 10; 134 private static final int MATCH_PREVIEW_PROGRAM = 11; 135 private static final int MATCH_PREVIEW_PROGRAM_ID = 12; 136 private static final int MATCH_WATCH_NEXT_PROGRAM = 13; 137 private static final int MATCH_WATCH_NEXT_PROGRAM_ID = 14; 138 139 private static final int MAX_LOGO_IMAGE_SIZE = 256; 140 141 private static final String EMPTY_STRING = ""; 142 143 private static final long MAX_PROGRAM_DATA_DELAY_IN_MILLIS = 10 * 1000; // 10 seconds 144 145 private static final Map<String, String> sChannelProjectionMap; 146 private static final Map<String, String> sProgramProjectionMap; 147 private static final Map<String, String> sWatchedProgramProjectionMap; 148 private static final Map<String, String> sRecordedProgramProjectionMap; 149 private static final Map<String, String> sPreviewProgramProjectionMap; 150 private static final Map<String, String> sWatchNextProgramProjectionMap; 151 private static boolean sInitialized; 152 153 static { 154 sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); sUriMatcher.addURI(TvContract.AUTHORITY, "channel", MATCH_CHANNEL)155 sUriMatcher.addURI(TvContract.AUTHORITY, "channel", MATCH_CHANNEL); sUriMatcher.addURI(TvContract.AUTHORITY, "channel/#", MATCH_CHANNEL_ID)156 sUriMatcher.addURI(TvContract.AUTHORITY, "channel/#", MATCH_CHANNEL_ID); sUriMatcher.addURI(TvContract.AUTHORITY, "channel/#/logo", MATCH_CHANNEL_ID_LOGO)157 sUriMatcher.addURI(TvContract.AUTHORITY, "channel/#/logo", MATCH_CHANNEL_ID_LOGO); sUriMatcher.addURI(TvContract.AUTHORITY, "passthrough/*", MATCH_PASSTHROUGH_ID)158 sUriMatcher.addURI(TvContract.AUTHORITY, "passthrough/*", MATCH_PASSTHROUGH_ID); sUriMatcher.addURI(TvContract.AUTHORITY, "program", MATCH_PROGRAM)159 sUriMatcher.addURI(TvContract.AUTHORITY, "program", MATCH_PROGRAM); sUriMatcher.addURI(TvContract.AUTHORITY, "program/#", MATCH_PROGRAM_ID)160 sUriMatcher.addURI(TvContract.AUTHORITY, "program/#", MATCH_PROGRAM_ID); sUriMatcher.addURI(TvContract.AUTHORITY, "watched_program", MATCH_WATCHED_PROGRAM)161 sUriMatcher.addURI(TvContract.AUTHORITY, "watched_program", MATCH_WATCHED_PROGRAM); sUriMatcher.addURI(TvContract.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID)162 sUriMatcher.addURI(TvContract.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID); sUriMatcher.addURI(TvContract.AUTHORITY, "recorded_program", MATCH_RECORDED_PROGRAM)163 sUriMatcher.addURI(TvContract.AUTHORITY, "recorded_program", MATCH_RECORDED_PROGRAM); sUriMatcher.addURI(TvContract.AUTHORITY, "recorded_program/#", MATCH_RECORDED_PROGRAM_ID)164 sUriMatcher.addURI(TvContract.AUTHORITY, "recorded_program/#", MATCH_RECORDED_PROGRAM_ID); sUriMatcher.addURI(TvContract.AUTHORITY, "preview_program", MATCH_PREVIEW_PROGRAM)165 sUriMatcher.addURI(TvContract.AUTHORITY, "preview_program", MATCH_PREVIEW_PROGRAM); sUriMatcher.addURI(TvContract.AUTHORITY, "preview_program/#", MATCH_PREVIEW_PROGRAM_ID)166 sUriMatcher.addURI(TvContract.AUTHORITY, "preview_program/#", MATCH_PREVIEW_PROGRAM_ID); sUriMatcher.addURI(TvContract.AUTHORITY, "watch_next_program", MATCH_WATCH_NEXT_PROGRAM)167 sUriMatcher.addURI(TvContract.AUTHORITY, "watch_next_program", MATCH_WATCH_NEXT_PROGRAM); sUriMatcher.addURI(TvContract.AUTHORITY, "watch_next_program/#", MATCH_WATCH_NEXT_PROGRAM_ID)168 sUriMatcher.addURI(TvContract.AUTHORITY, "watch_next_program/#", 169 MATCH_WATCH_NEXT_PROGRAM_ID); 170 171 sChannelProjectionMap = new HashMap<>(); sChannelProjectionMap.put(Channels._ID, CHANNELS_TABLE + "." + Channels._ID)172 sChannelProjectionMap.put(Channels._ID, CHANNELS_TABLE + "." + Channels._ID); sChannelProjectionMap.put(Channels.COLUMN_PACKAGE_NAME, CHANNELS_TABLE + "." + Channels.COLUMN_PACKAGE_NAME)173 sChannelProjectionMap.put(Channels.COLUMN_PACKAGE_NAME, 174 CHANNELS_TABLE + "." + Channels.COLUMN_PACKAGE_NAME); sChannelProjectionMap.put(Channels.COLUMN_INPUT_ID, CHANNELS_TABLE + "." + Channels.COLUMN_INPUT_ID)175 sChannelProjectionMap.put(Channels.COLUMN_INPUT_ID, 176 CHANNELS_TABLE + "." + Channels.COLUMN_INPUT_ID); sChannelProjectionMap.put(Channels.COLUMN_TYPE, CHANNELS_TABLE + "." + Channels.COLUMN_TYPE)177 sChannelProjectionMap.put(Channels.COLUMN_TYPE, 178 CHANNELS_TABLE + "." + Channels.COLUMN_TYPE); sChannelProjectionMap.put(Channels.COLUMN_SERVICE_TYPE, CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_TYPE)179 sChannelProjectionMap.put(Channels.COLUMN_SERVICE_TYPE, 180 CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_TYPE); sChannelProjectionMap.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, CHANNELS_TABLE + "." + Channels.COLUMN_ORIGINAL_NETWORK_ID)181 sChannelProjectionMap.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, 182 CHANNELS_TABLE + "." + Channels.COLUMN_ORIGINAL_NETWORK_ID); sChannelProjectionMap.put(Channels.COLUMN_TRANSPORT_STREAM_ID, CHANNELS_TABLE + "." + Channels.COLUMN_TRANSPORT_STREAM_ID)183 sChannelProjectionMap.put(Channels.COLUMN_TRANSPORT_STREAM_ID, 184 CHANNELS_TABLE + "." + Channels.COLUMN_TRANSPORT_STREAM_ID); sChannelProjectionMap.put(Channels.COLUMN_SERVICE_ID, CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_ID)185 sChannelProjectionMap.put(Channels.COLUMN_SERVICE_ID, 186 CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_ID); sChannelProjectionMap.put(Channels.COLUMN_DISPLAY_NUMBER, CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NUMBER)187 sChannelProjectionMap.put(Channels.COLUMN_DISPLAY_NUMBER, 188 CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NUMBER); sChannelProjectionMap.put(Channels.COLUMN_DISPLAY_NAME, CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NAME)189 sChannelProjectionMap.put(Channels.COLUMN_DISPLAY_NAME, 190 CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NAME); sChannelProjectionMap.put(Channels.COLUMN_NETWORK_AFFILIATION, CHANNELS_TABLE + "." + Channels.COLUMN_NETWORK_AFFILIATION)191 sChannelProjectionMap.put(Channels.COLUMN_NETWORK_AFFILIATION, 192 CHANNELS_TABLE + "." + Channels.COLUMN_NETWORK_AFFILIATION); sChannelProjectionMap.put(Channels.COLUMN_DESCRIPTION, CHANNELS_TABLE + "." + Channels.COLUMN_DESCRIPTION)193 sChannelProjectionMap.put(Channels.COLUMN_DESCRIPTION, 194 CHANNELS_TABLE + "." + Channels.COLUMN_DESCRIPTION); sChannelProjectionMap.put(Channels.COLUMN_VIDEO_FORMAT, CHANNELS_TABLE + "." + Channels.COLUMN_VIDEO_FORMAT)195 sChannelProjectionMap.put(Channels.COLUMN_VIDEO_FORMAT, 196 CHANNELS_TABLE + "." + Channels.COLUMN_VIDEO_FORMAT); sChannelProjectionMap.put(Channels.COLUMN_BROWSABLE, CHANNELS_TABLE + "." + Channels.COLUMN_BROWSABLE)197 sChannelProjectionMap.put(Channels.COLUMN_BROWSABLE, 198 CHANNELS_TABLE + "." + Channels.COLUMN_BROWSABLE); sChannelProjectionMap.put(Channels.COLUMN_SEARCHABLE, CHANNELS_TABLE + "." + Channels.COLUMN_SEARCHABLE)199 sChannelProjectionMap.put(Channels.COLUMN_SEARCHABLE, 200 CHANNELS_TABLE + "." + Channels.COLUMN_SEARCHABLE); sChannelProjectionMap.put(Channels.COLUMN_LOCKED, CHANNELS_TABLE + "." + Channels.COLUMN_LOCKED)201 sChannelProjectionMap.put(Channels.COLUMN_LOCKED, 202 CHANNELS_TABLE + "." + Channels.COLUMN_LOCKED); sChannelProjectionMap.put(Channels.COLUMN_APP_LINK_ICON_URI, CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_ICON_URI)203 sChannelProjectionMap.put(Channels.COLUMN_APP_LINK_ICON_URI, 204 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)205 sChannelProjectionMap.put(Channels.COLUMN_APP_LINK_POSTER_ART_URI, 206 CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_POSTER_ART_URI); sChannelProjectionMap.put(Channels.COLUMN_APP_LINK_TEXT, CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_TEXT)207 sChannelProjectionMap.put(Channels.COLUMN_APP_LINK_TEXT, 208 CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_TEXT); sChannelProjectionMap.put(Channels.COLUMN_APP_LINK_COLOR, CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_COLOR)209 sChannelProjectionMap.put(Channels.COLUMN_APP_LINK_COLOR, 210 CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_COLOR); sChannelProjectionMap.put(Channels.COLUMN_APP_LINK_INTENT_URI, CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_INTENT_URI)211 sChannelProjectionMap.put(Channels.COLUMN_APP_LINK_INTENT_URI, 212 CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_INTENT_URI); sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_DATA, CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_DATA)213 sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_DATA, 214 CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_DATA); sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG1, CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1)215 sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG1, 216 CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1); sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2)217 sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, 218 CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2); sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG3, CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3)219 sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG3, 220 CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3); sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG4, CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4)221 sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_FLAG4, 222 CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4); sChannelProjectionMap.put(Channels.COLUMN_VERSION_NUMBER, CHANNELS_TABLE + "." + Channels.COLUMN_VERSION_NUMBER)223 sChannelProjectionMap.put(Channels.COLUMN_VERSION_NUMBER, 224 CHANNELS_TABLE + "." + Channels.COLUMN_VERSION_NUMBER); sChannelProjectionMap.put(Channels.COLUMN_TRANSIENT, CHANNELS_TABLE + "." + Channels.COLUMN_TRANSIENT)225 sChannelProjectionMap.put(Channels.COLUMN_TRANSIENT, 226 CHANNELS_TABLE + "." + Channels.COLUMN_TRANSIENT); sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_ID, CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_ID)227 sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_ID, 228 CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_ID); 229 230 sProgramProjectionMap = new HashMap<>(); sProgramProjectionMap.put(Programs._ID, Programs._ID)231 sProgramProjectionMap.put(Programs._ID, Programs._ID); sProgramProjectionMap.put(Programs.COLUMN_PACKAGE_NAME, Programs.COLUMN_PACKAGE_NAME)232 sProgramProjectionMap.put(Programs.COLUMN_PACKAGE_NAME, Programs.COLUMN_PACKAGE_NAME); sProgramProjectionMap.put(Programs.COLUMN_CHANNEL_ID, Programs.COLUMN_CHANNEL_ID)233 sProgramProjectionMap.put(Programs.COLUMN_CHANNEL_ID, Programs.COLUMN_CHANNEL_ID); sProgramProjectionMap.put(Programs.COLUMN_TITLE, Programs.COLUMN_TITLE)234 sProgramProjectionMap.put(Programs.COLUMN_TITLE, Programs.COLUMN_TITLE); 235 // 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)236 sProgramProjectionMap.put(Programs.COLUMN_SEASON_NUMBER, 237 Programs.COLUMN_SEASON_DISPLAY_NUMBER + " AS " + Programs.COLUMN_SEASON_NUMBER); sProgramProjectionMap.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, Programs.COLUMN_SEASON_DISPLAY_NUMBER)238 sProgramProjectionMap.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, 239 Programs.COLUMN_SEASON_DISPLAY_NUMBER); sProgramProjectionMap.put(Programs.COLUMN_SEASON_TITLE, Programs.COLUMN_SEASON_TITLE)240 sProgramProjectionMap.put(Programs.COLUMN_SEASON_TITLE, Programs.COLUMN_SEASON_TITLE); 241 // 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)242 sProgramProjectionMap.put(Programs.COLUMN_EPISODE_NUMBER, 243 Programs.COLUMN_EPISODE_DISPLAY_NUMBER + " AS " + Programs.COLUMN_EPISODE_NUMBER); sProgramProjectionMap.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, Programs.COLUMN_EPISODE_DISPLAY_NUMBER)244 sProgramProjectionMap.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, 245 Programs.COLUMN_EPISODE_DISPLAY_NUMBER); sProgramProjectionMap.put(Programs.COLUMN_EPISODE_TITLE, Programs.COLUMN_EPISODE_TITLE)246 sProgramProjectionMap.put(Programs.COLUMN_EPISODE_TITLE, Programs.COLUMN_EPISODE_TITLE); sProgramProjectionMap.put(Programs.COLUMN_START_TIME_UTC_MILLIS, Programs.COLUMN_START_TIME_UTC_MILLIS)247 sProgramProjectionMap.put(Programs.COLUMN_START_TIME_UTC_MILLIS, 248 Programs.COLUMN_START_TIME_UTC_MILLIS); sProgramProjectionMap.put(Programs.COLUMN_END_TIME_UTC_MILLIS, Programs.COLUMN_END_TIME_UTC_MILLIS)249 sProgramProjectionMap.put(Programs.COLUMN_END_TIME_UTC_MILLIS, 250 Programs.COLUMN_END_TIME_UTC_MILLIS); sProgramProjectionMap.put(Programs.COLUMN_BROADCAST_GENRE, Programs.COLUMN_BROADCAST_GENRE)251 sProgramProjectionMap.put(Programs.COLUMN_BROADCAST_GENRE, Programs.COLUMN_BROADCAST_GENRE); sProgramProjectionMap.put(Programs.COLUMN_CANONICAL_GENRE, Programs.COLUMN_CANONICAL_GENRE)252 sProgramProjectionMap.put(Programs.COLUMN_CANONICAL_GENRE, Programs.COLUMN_CANONICAL_GENRE); sProgramProjectionMap.put(Programs.COLUMN_SHORT_DESCRIPTION, Programs.COLUMN_SHORT_DESCRIPTION)253 sProgramProjectionMap.put(Programs.COLUMN_SHORT_DESCRIPTION, 254 Programs.COLUMN_SHORT_DESCRIPTION); sProgramProjectionMap.put(Programs.COLUMN_LONG_DESCRIPTION, Programs.COLUMN_LONG_DESCRIPTION)255 sProgramProjectionMap.put(Programs.COLUMN_LONG_DESCRIPTION, 256 Programs.COLUMN_LONG_DESCRIPTION); sProgramProjectionMap.put(Programs.COLUMN_VIDEO_WIDTH, Programs.COLUMN_VIDEO_WIDTH)257 sProgramProjectionMap.put(Programs.COLUMN_VIDEO_WIDTH, Programs.COLUMN_VIDEO_WIDTH); sProgramProjectionMap.put(Programs.COLUMN_VIDEO_HEIGHT, Programs.COLUMN_VIDEO_HEIGHT)258 sProgramProjectionMap.put(Programs.COLUMN_VIDEO_HEIGHT, Programs.COLUMN_VIDEO_HEIGHT); sProgramProjectionMap.put(Programs.COLUMN_AUDIO_LANGUAGE, Programs.COLUMN_AUDIO_LANGUAGE)259 sProgramProjectionMap.put(Programs.COLUMN_AUDIO_LANGUAGE, Programs.COLUMN_AUDIO_LANGUAGE); sProgramProjectionMap.put(Programs.COLUMN_CONTENT_RATING, Programs.COLUMN_CONTENT_RATING)260 sProgramProjectionMap.put(Programs.COLUMN_CONTENT_RATING, Programs.COLUMN_CONTENT_RATING); sProgramProjectionMap.put(Programs.COLUMN_POSTER_ART_URI, Programs.COLUMN_POSTER_ART_URI)261 sProgramProjectionMap.put(Programs.COLUMN_POSTER_ART_URI, Programs.COLUMN_POSTER_ART_URI); sProgramProjectionMap.put(Programs.COLUMN_THUMBNAIL_URI, Programs.COLUMN_THUMBNAIL_URI)262 sProgramProjectionMap.put(Programs.COLUMN_THUMBNAIL_URI, Programs.COLUMN_THUMBNAIL_URI); sProgramProjectionMap.put(Programs.COLUMN_SEARCHABLE, Programs.COLUMN_SEARCHABLE)263 sProgramProjectionMap.put(Programs.COLUMN_SEARCHABLE, Programs.COLUMN_SEARCHABLE); sProgramProjectionMap.put(Programs.COLUMN_RECORDING_PROHIBITED, Programs.COLUMN_RECORDING_PROHIBITED)264 sProgramProjectionMap.put(Programs.COLUMN_RECORDING_PROHIBITED, 265 Programs.COLUMN_RECORDING_PROHIBITED); sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_DATA, Programs.COLUMN_INTERNAL_PROVIDER_DATA)266 sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_DATA, 267 Programs.COLUMN_INTERNAL_PROVIDER_DATA); sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_FLAG1, Programs.COLUMN_INTERNAL_PROVIDER_FLAG1)268 sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_FLAG1, 269 Programs.COLUMN_INTERNAL_PROVIDER_FLAG1); sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_FLAG2, Programs.COLUMN_INTERNAL_PROVIDER_FLAG2)270 sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_FLAG2, 271 Programs.COLUMN_INTERNAL_PROVIDER_FLAG2); sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_FLAG3, Programs.COLUMN_INTERNAL_PROVIDER_FLAG3)272 sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_FLAG3, 273 Programs.COLUMN_INTERNAL_PROVIDER_FLAG3); sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_FLAG4, Programs.COLUMN_INTERNAL_PROVIDER_FLAG4)274 sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_FLAG4, 275 Programs.COLUMN_INTERNAL_PROVIDER_FLAG4); sProgramProjectionMap.put(Programs.COLUMN_VERSION_NUMBER, Programs.COLUMN_VERSION_NUMBER)276 sProgramProjectionMap.put(Programs.COLUMN_VERSION_NUMBER, Programs.COLUMN_VERSION_NUMBER); sProgramProjectionMap.put(Programs.COLUMN_REVIEW_RATING_STYLE, Programs.COLUMN_REVIEW_RATING_STYLE)277 sProgramProjectionMap.put(Programs.COLUMN_REVIEW_RATING_STYLE, 278 Programs.COLUMN_REVIEW_RATING_STYLE); sProgramProjectionMap.put(Programs.COLUMN_REVIEW_RATING, Programs.COLUMN_REVIEW_RATING)279 sProgramProjectionMap.put(Programs.COLUMN_REVIEW_RATING, 280 Programs.COLUMN_REVIEW_RATING); 281 282 sWatchedProgramProjectionMap = new HashMap<>(); sWatchedProgramProjectionMap.put(WatchedPrograms._ID, WatchedPrograms._ID)283 sWatchedProgramProjectionMap.put(WatchedPrograms._ID, WatchedPrograms._ID); sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS)284 sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, 285 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS); sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS)286 sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 287 WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS); sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_CHANNEL_ID, WatchedPrograms.COLUMN_CHANNEL_ID)288 sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_CHANNEL_ID, 289 WatchedPrograms.COLUMN_CHANNEL_ID); sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_TITLE, WatchedPrograms.COLUMN_TITLE)290 sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_TITLE, 291 WatchedPrograms.COLUMN_TITLE); sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS)292 sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, 293 WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS); sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS)294 sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, 295 WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS); sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_DESCRIPTION, WatchedPrograms.COLUMN_DESCRIPTION)296 sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_DESCRIPTION, 297 WatchedPrograms.COLUMN_DESCRIPTION); sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS, WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS)298 sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS, 299 WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS); sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN, WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN)300 sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN, 301 WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN); sWatchedProgramProjectionMap.put(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED, WATCHED_PROGRAMS_COLUMN_CONSOLIDATED)302 sWatchedProgramProjectionMap.put(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED, 303 WATCHED_PROGRAMS_COLUMN_CONSOLIDATED); 304 305 sRecordedProgramProjectionMap = new HashMap<>(); sRecordedProgramProjectionMap.put(RecordedPrograms._ID, RecordedPrograms._ID)306 sRecordedProgramProjectionMap.put(RecordedPrograms._ID, RecordedPrograms._ID); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_PACKAGE_NAME, RecordedPrograms.COLUMN_PACKAGE_NAME)307 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_PACKAGE_NAME, 308 RecordedPrograms.COLUMN_PACKAGE_NAME); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INPUT_ID, RecordedPrograms.COLUMN_INPUT_ID)309 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INPUT_ID, 310 RecordedPrograms.COLUMN_INPUT_ID); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_CHANNEL_ID, RecordedPrograms.COLUMN_CHANNEL_ID)311 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_CHANNEL_ID, 312 RecordedPrograms.COLUMN_CHANNEL_ID); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_TITLE, RecordedPrograms.COLUMN_TITLE)313 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_TITLE, 314 RecordedPrograms.COLUMN_TITLE); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER)315 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, 316 RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_SEASON_TITLE, RecordedPrograms.COLUMN_SEASON_TITLE)317 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_SEASON_TITLE, 318 RecordedPrograms.COLUMN_SEASON_TITLE); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER)319 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, 320 RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_EPISODE_TITLE, RecordedPrograms.COLUMN_EPISODE_TITLE)321 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_EPISODE_TITLE, 322 RecordedPrograms.COLUMN_EPISODE_TITLE); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS)323 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, 324 RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS)325 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, 326 RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_BROADCAST_GENRE, RecordedPrograms.COLUMN_BROADCAST_GENRE)327 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_BROADCAST_GENRE, 328 RecordedPrograms.COLUMN_BROADCAST_GENRE); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_CANONICAL_GENRE, RecordedPrograms.COLUMN_CANONICAL_GENRE)329 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_CANONICAL_GENRE, 330 RecordedPrograms.COLUMN_CANONICAL_GENRE); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, RecordedPrograms.COLUMN_SHORT_DESCRIPTION)331 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, 332 RecordedPrograms.COLUMN_SHORT_DESCRIPTION); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, RecordedPrograms.COLUMN_LONG_DESCRIPTION)333 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, 334 RecordedPrograms.COLUMN_LONG_DESCRIPTION); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, RecordedPrograms.COLUMN_VIDEO_WIDTH)335 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, 336 RecordedPrograms.COLUMN_VIDEO_WIDTH); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, RecordedPrograms.COLUMN_VIDEO_HEIGHT)337 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, 338 RecordedPrograms.COLUMN_VIDEO_HEIGHT); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, RecordedPrograms.COLUMN_AUDIO_LANGUAGE)339 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, 340 RecordedPrograms.COLUMN_AUDIO_LANGUAGE); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_CONTENT_RATING, RecordedPrograms.COLUMN_CONTENT_RATING)341 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_CONTENT_RATING, 342 RecordedPrograms.COLUMN_CONTENT_RATING); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_POSTER_ART_URI, RecordedPrograms.COLUMN_POSTER_ART_URI)343 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_POSTER_ART_URI, 344 RecordedPrograms.COLUMN_POSTER_ART_URI); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, RecordedPrograms.COLUMN_THUMBNAIL_URI)345 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, 346 RecordedPrograms.COLUMN_THUMBNAIL_URI); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_SEARCHABLE, RecordedPrograms.COLUMN_SEARCHABLE)347 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_SEARCHABLE, 348 RecordedPrograms.COLUMN_SEARCHABLE); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, RecordedPrograms.COLUMN_RECORDING_DATA_URI)349 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, 350 RecordedPrograms.COLUMN_RECORDING_DATA_URI); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, RecordedPrograms.COLUMN_RECORDING_DATA_BYTES)351 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, 352 RecordedPrograms.COLUMN_RECORDING_DATA_BYTES); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS)353 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, 354 RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS)355 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, 356 RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA)357 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, 358 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1)359 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, 360 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2)361 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 362 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3)363 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, 364 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4)365 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, 366 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_VERSION_NUMBER, RecordedPrograms.COLUMN_VERSION_NUMBER)367 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_VERSION_NUMBER, 368 RecordedPrograms.COLUMN_VERSION_NUMBER); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_REVIEW_RATING_STYLE, RecordedPrograms.COLUMN_REVIEW_RATING_STYLE)369 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_REVIEW_RATING_STYLE, 370 RecordedPrograms.COLUMN_REVIEW_RATING_STYLE); sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_REVIEW_RATING, RecordedPrograms.COLUMN_REVIEW_RATING)371 sRecordedProgramProjectionMap.put(RecordedPrograms.COLUMN_REVIEW_RATING, 372 RecordedPrograms.COLUMN_REVIEW_RATING); 373 374 sPreviewProgramProjectionMap = new HashMap<>(); sPreviewProgramProjectionMap.put(PreviewPrograms._ID, PreviewPrograms._ID)375 sPreviewProgramProjectionMap.put(PreviewPrograms._ID, PreviewPrograms._ID); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_PACKAGE_NAME, PreviewPrograms.COLUMN_PACKAGE_NAME)376 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_PACKAGE_NAME, 377 PreviewPrograms.COLUMN_PACKAGE_NAME); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_CHANNEL_ID, PreviewPrograms.COLUMN_CHANNEL_ID)378 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_CHANNEL_ID, 379 PreviewPrograms.COLUMN_CHANNEL_ID); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_TITLE, PreviewPrograms.COLUMN_TITLE)380 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_TITLE, 381 PreviewPrograms.COLUMN_TITLE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER, PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER)382 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER, 383 PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_SEASON_TITLE, PreviewPrograms.COLUMN_SEASON_TITLE)384 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_SEASON_TITLE, 385 PreviewPrograms.COLUMN_SEASON_TITLE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER)386 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, 387 PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_EPISODE_TITLE, PreviewPrograms.COLUMN_EPISODE_TITLE)388 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_EPISODE_TITLE, 389 PreviewPrograms.COLUMN_EPISODE_TITLE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_CANONICAL_GENRE, PreviewPrograms.COLUMN_CANONICAL_GENRE)390 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_CANONICAL_GENRE, 391 PreviewPrograms.COLUMN_CANONICAL_GENRE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_SHORT_DESCRIPTION, PreviewPrograms.COLUMN_SHORT_DESCRIPTION)392 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_SHORT_DESCRIPTION, 393 PreviewPrograms.COLUMN_SHORT_DESCRIPTION); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LONG_DESCRIPTION, PreviewPrograms.COLUMN_LONG_DESCRIPTION)394 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LONG_DESCRIPTION, 395 PreviewPrograms.COLUMN_LONG_DESCRIPTION); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_VIDEO_WIDTH, PreviewPrograms.COLUMN_VIDEO_WIDTH)396 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_VIDEO_WIDTH, 397 PreviewPrograms.COLUMN_VIDEO_WIDTH); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_VIDEO_HEIGHT, PreviewPrograms.COLUMN_VIDEO_HEIGHT)398 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_VIDEO_HEIGHT, 399 PreviewPrograms.COLUMN_VIDEO_HEIGHT); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_AUDIO_LANGUAGE, PreviewPrograms.COLUMN_AUDIO_LANGUAGE)400 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_AUDIO_LANGUAGE, 401 PreviewPrograms.COLUMN_AUDIO_LANGUAGE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_CONTENT_RATING, PreviewPrograms.COLUMN_CONTENT_RATING)402 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_CONTENT_RATING, 403 PreviewPrograms.COLUMN_CONTENT_RATING); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_POSTER_ART_URI, PreviewPrograms.COLUMN_POSTER_ART_URI)404 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_POSTER_ART_URI, 405 PreviewPrograms.COLUMN_POSTER_ART_URI); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_THUMBNAIL_URI, PreviewPrograms.COLUMN_THUMBNAIL_URI)406 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_THUMBNAIL_URI, 407 PreviewPrograms.COLUMN_THUMBNAIL_URI); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_SEARCHABLE, PreviewPrograms.COLUMN_SEARCHABLE)408 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_SEARCHABLE, 409 PreviewPrograms.COLUMN_SEARCHABLE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA, PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA)410 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA, 411 PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1)412 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, 413 PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2)414 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 415 PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3)416 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, 417 PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4)418 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, 419 PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_VERSION_NUMBER, PreviewPrograms.COLUMN_VERSION_NUMBER)420 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_VERSION_NUMBER, 421 PreviewPrograms.COLUMN_VERSION_NUMBER); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID, PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID)422 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID, 423 PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI, PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI)424 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI, 425 PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS)426 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, 427 PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_DURATION_MILLIS, PreviewPrograms.COLUMN_DURATION_MILLIS)428 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_DURATION_MILLIS, 429 PreviewPrograms.COLUMN_DURATION_MILLIS); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTENT_URI, PreviewPrograms.COLUMN_INTENT_URI)430 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTENT_URI, 431 PreviewPrograms.COLUMN_INTENT_URI); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_WEIGHT, PreviewPrograms.COLUMN_WEIGHT)432 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_WEIGHT, 433 PreviewPrograms.COLUMN_WEIGHT); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_TRANSIENT, PreviewPrograms.COLUMN_TRANSIENT)434 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_TRANSIENT, 435 PreviewPrograms.COLUMN_TRANSIENT); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_TYPE, PreviewPrograms.COLUMN_TYPE)436 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_TYPE, PreviewPrograms.COLUMN_TYPE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO, PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO)437 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO, 438 PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO, PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO)439 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO, 440 PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LOGO_URI, PreviewPrograms.COLUMN_LOGO_URI)441 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LOGO_URI, 442 PreviewPrograms.COLUMN_LOGO_URI); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_AVAILABILITY, PreviewPrograms.COLUMN_AVAILABILITY)443 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_AVAILABILITY, 444 PreviewPrograms.COLUMN_AVAILABILITY); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_STARTING_PRICE, PreviewPrograms.COLUMN_STARTING_PRICE)445 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_STARTING_PRICE, 446 PreviewPrograms.COLUMN_STARTING_PRICE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_OFFER_PRICE, PreviewPrograms.COLUMN_OFFER_PRICE)447 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_OFFER_PRICE, 448 PreviewPrograms.COLUMN_OFFER_PRICE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_RELEASE_DATE, PreviewPrograms.COLUMN_RELEASE_DATE)449 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_RELEASE_DATE, 450 PreviewPrograms.COLUMN_RELEASE_DATE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_ITEM_COUNT, PreviewPrograms.COLUMN_ITEM_COUNT)451 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_ITEM_COUNT, 452 PreviewPrograms.COLUMN_ITEM_COUNT); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LIVE, PreviewPrograms.COLUMN_LIVE)453 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LIVE, PreviewPrograms.COLUMN_LIVE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERACTION_TYPE, PreviewPrograms.COLUMN_INTERACTION_TYPE)454 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERACTION_TYPE, 455 PreviewPrograms.COLUMN_INTERACTION_TYPE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERACTION_COUNT, PreviewPrograms.COLUMN_INTERACTION_COUNT)456 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_INTERACTION_COUNT, 457 PreviewPrograms.COLUMN_INTERACTION_COUNT); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_AUTHOR, PreviewPrograms.COLUMN_AUTHOR)458 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_AUTHOR, 459 PreviewPrograms.COLUMN_AUTHOR); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_REVIEW_RATING_STYLE, PreviewPrograms.COLUMN_REVIEW_RATING_STYLE)460 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_REVIEW_RATING_STYLE, 461 PreviewPrograms.COLUMN_REVIEW_RATING_STYLE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_REVIEW_RATING, PreviewPrograms.COLUMN_REVIEW_RATING)462 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_REVIEW_RATING, 463 PreviewPrograms.COLUMN_REVIEW_RATING); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_BROWSABLE, PreviewPrograms.COLUMN_BROWSABLE)464 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_BROWSABLE, 465 PreviewPrograms.COLUMN_BROWSABLE); sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_CONTENT_ID, PreviewPrograms.COLUMN_CONTENT_ID)466 sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_CONTENT_ID, 467 PreviewPrograms.COLUMN_CONTENT_ID); 468 469 sWatchNextProgramProjectionMap = new HashMap<>(); sWatchNextProgramProjectionMap.put(WatchNextPrograms._ID, WatchNextPrograms._ID)470 sWatchNextProgramProjectionMap.put(WatchNextPrograms._ID, WatchNextPrograms._ID); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_PACKAGE_NAME, WatchNextPrograms.COLUMN_PACKAGE_NAME)471 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_PACKAGE_NAME, 472 WatchNextPrograms.COLUMN_PACKAGE_NAME); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_TITLE, WatchNextPrograms.COLUMN_TITLE)473 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_TITLE, 474 WatchNextPrograms.COLUMN_TITLE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER, WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER)475 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER, 476 WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_SEASON_TITLE, WatchNextPrograms.COLUMN_SEASON_TITLE)477 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_SEASON_TITLE, 478 WatchNextPrograms.COLUMN_SEASON_TITLE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER)479 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, 480 WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_EPISODE_TITLE, WatchNextPrograms.COLUMN_EPISODE_TITLE)481 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_EPISODE_TITLE, 482 WatchNextPrograms.COLUMN_EPISODE_TITLE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_CANONICAL_GENRE, WatchNextPrograms.COLUMN_CANONICAL_GENRE)483 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_CANONICAL_GENRE, 484 WatchNextPrograms.COLUMN_CANONICAL_GENRE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_SHORT_DESCRIPTION, WatchNextPrograms.COLUMN_SHORT_DESCRIPTION)485 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_SHORT_DESCRIPTION, 486 WatchNextPrograms.COLUMN_SHORT_DESCRIPTION); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_LONG_DESCRIPTION, WatchNextPrograms.COLUMN_LONG_DESCRIPTION)487 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_LONG_DESCRIPTION, 488 WatchNextPrograms.COLUMN_LONG_DESCRIPTION); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_VIDEO_WIDTH, WatchNextPrograms.COLUMN_VIDEO_WIDTH)489 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_VIDEO_WIDTH, 490 WatchNextPrograms.COLUMN_VIDEO_WIDTH); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_VIDEO_HEIGHT, WatchNextPrograms.COLUMN_VIDEO_HEIGHT)491 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_VIDEO_HEIGHT, 492 WatchNextPrograms.COLUMN_VIDEO_HEIGHT); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_AUDIO_LANGUAGE, WatchNextPrograms.COLUMN_AUDIO_LANGUAGE)493 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_AUDIO_LANGUAGE, 494 WatchNextPrograms.COLUMN_AUDIO_LANGUAGE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_CONTENT_RATING, WatchNextPrograms.COLUMN_CONTENT_RATING)495 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_CONTENT_RATING, 496 WatchNextPrograms.COLUMN_CONTENT_RATING); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_POSTER_ART_URI, WatchNextPrograms.COLUMN_POSTER_ART_URI)497 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_POSTER_ART_URI, 498 WatchNextPrograms.COLUMN_POSTER_ART_URI); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_THUMBNAIL_URI, WatchNextPrograms.COLUMN_THUMBNAIL_URI)499 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_THUMBNAIL_URI, 500 WatchNextPrograms.COLUMN_THUMBNAIL_URI); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_SEARCHABLE, WatchNextPrograms.COLUMN_SEARCHABLE)501 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_SEARCHABLE, 502 WatchNextPrograms.COLUMN_SEARCHABLE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA, WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA)503 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA, 504 WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1)505 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, 506 WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2)507 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 508 WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3)509 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, 510 WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4)511 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, 512 WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_VERSION_NUMBER, WatchNextPrograms.COLUMN_VERSION_NUMBER)513 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_VERSION_NUMBER, 514 WatchNextPrograms.COLUMN_VERSION_NUMBER); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID, WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID)515 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID, 516 WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI, WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI)517 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI, 518 WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS)519 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, 520 WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_DURATION_MILLIS, WatchNextPrograms.COLUMN_DURATION_MILLIS)521 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_DURATION_MILLIS, 522 WatchNextPrograms.COLUMN_DURATION_MILLIS); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTENT_URI, WatchNextPrograms.COLUMN_INTENT_URI)523 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTENT_URI, 524 WatchNextPrograms.COLUMN_INTENT_URI); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_TRANSIENT, WatchNextPrograms.COLUMN_TRANSIENT)525 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_TRANSIENT, 526 WatchNextPrograms.COLUMN_TRANSIENT); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_TYPE, WatchNextPrograms.COLUMN_TYPE)527 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_TYPE, 528 WatchNextPrograms.COLUMN_TYPE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE, WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE)529 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE, 530 WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO, WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO)531 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO, 532 WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO, WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO)533 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO, 534 WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_LOGO_URI, WatchNextPrograms.COLUMN_LOGO_URI)535 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_LOGO_URI, 536 WatchNextPrograms.COLUMN_LOGO_URI); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_AVAILABILITY, WatchNextPrograms.COLUMN_AVAILABILITY)537 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_AVAILABILITY, 538 WatchNextPrograms.COLUMN_AVAILABILITY); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_STARTING_PRICE, WatchNextPrograms.COLUMN_STARTING_PRICE)539 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_STARTING_PRICE, 540 WatchNextPrograms.COLUMN_STARTING_PRICE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_OFFER_PRICE, WatchNextPrograms.COLUMN_OFFER_PRICE)541 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_OFFER_PRICE, 542 WatchNextPrograms.COLUMN_OFFER_PRICE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_RELEASE_DATE, WatchNextPrograms.COLUMN_RELEASE_DATE)543 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_RELEASE_DATE, 544 WatchNextPrograms.COLUMN_RELEASE_DATE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_ITEM_COUNT, WatchNextPrograms.COLUMN_ITEM_COUNT)545 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_ITEM_COUNT, 546 WatchNextPrograms.COLUMN_ITEM_COUNT); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_LIVE, WatchNextPrograms.COLUMN_LIVE)547 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_LIVE, 548 WatchNextPrograms.COLUMN_LIVE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERACTION_TYPE, WatchNextPrograms.COLUMN_INTERACTION_TYPE)549 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERACTION_TYPE, 550 WatchNextPrograms.COLUMN_INTERACTION_TYPE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERACTION_COUNT, WatchNextPrograms.COLUMN_INTERACTION_COUNT)551 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_INTERACTION_COUNT, 552 WatchNextPrograms.COLUMN_INTERACTION_COUNT); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_AUTHOR, WatchNextPrograms.COLUMN_AUTHOR)553 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_AUTHOR, 554 WatchNextPrograms.COLUMN_AUTHOR); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE, WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE)555 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE, 556 WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_REVIEW_RATING, WatchNextPrograms.COLUMN_REVIEW_RATING)557 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_REVIEW_RATING, 558 WatchNextPrograms.COLUMN_REVIEW_RATING); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_BROWSABLE, WatchNextPrograms.COLUMN_BROWSABLE)559 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_BROWSABLE, 560 WatchNextPrograms.COLUMN_BROWSABLE); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_CONTENT_ID, WatchNextPrograms.COLUMN_CONTENT_ID)561 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_CONTENT_ID, 562 WatchNextPrograms.COLUMN_CONTENT_ID); sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS, WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS)563 sWatchNextProgramProjectionMap.put(WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS, 564 WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS); 565 } 566 567 // Mapping from broadcast genre to canonical genre. 568 private static Map<String, String> sGenreMap; 569 570 private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS"; 571 572 private static final String PERMISSION_ACCESS_ALL_EPG_DATA = 573 "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"; 574 575 private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS = 576 "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"; 577 578 private static final String CREATE_RECORDED_PROGRAMS_TABLE_SQL = 579 "CREATE TABLE " + RECORDED_PROGRAMS_TABLE + " (" 580 + RecordedPrograms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," 581 + RecordedPrograms.COLUMN_PACKAGE_NAME + " TEXT NOT NULL," 582 + RecordedPrograms.COLUMN_INPUT_ID + " TEXT NOT NULL," 583 + RecordedPrograms.COLUMN_CHANNEL_ID + " INTEGER," 584 + RecordedPrograms.COLUMN_TITLE + " TEXT," 585 + RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER + " TEXT," 586 + RecordedPrograms.COLUMN_SEASON_TITLE + " TEXT," 587 + RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER + " TEXT," 588 + RecordedPrograms.COLUMN_EPISODE_TITLE + " TEXT," 589 + RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS + " INTEGER," 590 + RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS + " INTEGER," 591 + RecordedPrograms.COLUMN_BROADCAST_GENRE + " TEXT," 592 + RecordedPrograms.COLUMN_CANONICAL_GENRE + " TEXT," 593 + RecordedPrograms.COLUMN_SHORT_DESCRIPTION + " TEXT," 594 + RecordedPrograms.COLUMN_LONG_DESCRIPTION + " TEXT," 595 + RecordedPrograms.COLUMN_VIDEO_WIDTH + " INTEGER," 596 + RecordedPrograms.COLUMN_VIDEO_HEIGHT + " INTEGER," 597 + RecordedPrograms.COLUMN_AUDIO_LANGUAGE + " TEXT," 598 + RecordedPrograms.COLUMN_CONTENT_RATING + " TEXT," 599 + RecordedPrograms.COLUMN_POSTER_ART_URI + " TEXT," 600 + RecordedPrograms.COLUMN_THUMBNAIL_URI + " TEXT," 601 + RecordedPrograms.COLUMN_SEARCHABLE + " INTEGER NOT NULL DEFAULT 1," 602 + RecordedPrograms.COLUMN_RECORDING_DATA_URI + " TEXT," 603 + RecordedPrograms.COLUMN_RECORDING_DATA_BYTES + " INTEGER," 604 + RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS + " INTEGER," 605 + RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS + " INTEGER," 606 + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA + " BLOB," 607 + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1 + " INTEGER," 608 + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2 + " INTEGER," 609 + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3 + " INTEGER," 610 + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4 + " INTEGER," 611 + RecordedPrograms.COLUMN_VERSION_NUMBER + " INTEGER," 612 + RecordedPrograms.COLUMN_REVIEW_RATING_STYLE + " INTEGER," 613 + RecordedPrograms.COLUMN_REVIEW_RATING + " TEXT," 614 + "FOREIGN KEY(" + RecordedPrograms.COLUMN_CHANNEL_ID + ") " 615 + "REFERENCES " + CHANNELS_TABLE + "(" + Channels._ID + ") " 616 + "ON UPDATE CASCADE ON DELETE SET NULL);"; 617 618 private static final String CREATE_PREVIEW_PROGRAMS_TABLE_SQL = 619 "CREATE TABLE " + PREVIEW_PROGRAMS_TABLE + " (" 620 + PreviewPrograms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," 621 + PreviewPrograms.COLUMN_PACKAGE_NAME + " TEXT NOT NULL," 622 + PreviewPrograms.COLUMN_CHANNEL_ID + " INTEGER," 623 + PreviewPrograms.COLUMN_TITLE + " TEXT," 624 + PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER + " TEXT," 625 + PreviewPrograms.COLUMN_SEASON_TITLE + " TEXT," 626 + PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER + " TEXT," 627 + PreviewPrograms.COLUMN_EPISODE_TITLE + " TEXT," 628 + PreviewPrograms.COLUMN_CANONICAL_GENRE + " TEXT," 629 + PreviewPrograms.COLUMN_SHORT_DESCRIPTION + " TEXT," 630 + PreviewPrograms.COLUMN_LONG_DESCRIPTION + " TEXT," 631 + PreviewPrograms.COLUMN_VIDEO_WIDTH + " INTEGER," 632 + PreviewPrograms.COLUMN_VIDEO_HEIGHT + " INTEGER," 633 + PreviewPrograms.COLUMN_AUDIO_LANGUAGE + " TEXT," 634 + PreviewPrograms.COLUMN_CONTENT_RATING + " TEXT," 635 + PreviewPrograms.COLUMN_POSTER_ART_URI + " TEXT," 636 + PreviewPrograms.COLUMN_THUMBNAIL_URI + " TEXT," 637 + PreviewPrograms.COLUMN_SEARCHABLE + " INTEGER NOT NULL DEFAULT 1," 638 + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA + " BLOB," 639 + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1 + " INTEGER," 640 + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2 + " INTEGER," 641 + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3 + " INTEGER," 642 + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4 + " INTEGER," 643 + PreviewPrograms.COLUMN_VERSION_NUMBER + " INTEGER," 644 + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID + " TEXT," 645 + PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI + " TEXT," 646 + PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS + " INTEGER," 647 + PreviewPrograms.COLUMN_DURATION_MILLIS + " INTEGER," 648 + PreviewPrograms.COLUMN_INTENT_URI + " TEXT," 649 + PreviewPrograms.COLUMN_WEIGHT + " INTEGER," 650 + PreviewPrograms.COLUMN_TRANSIENT + " INTEGER NOT NULL DEFAULT 0," 651 + PreviewPrograms.COLUMN_TYPE + " INTEGER NOT NULL," 652 + PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO + " INTEGER," 653 + PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO + " INTEGER," 654 + PreviewPrograms.COLUMN_LOGO_URI + " TEXT," 655 + PreviewPrograms.COLUMN_AVAILABILITY + " INTERGER," 656 + PreviewPrograms.COLUMN_STARTING_PRICE + " TEXT," 657 + PreviewPrograms.COLUMN_OFFER_PRICE + " TEXT," 658 + PreviewPrograms.COLUMN_RELEASE_DATE + " TEXT," 659 + PreviewPrograms.COLUMN_ITEM_COUNT + " INTEGER," 660 + PreviewPrograms.COLUMN_LIVE + " INTEGER NOT NULL DEFAULT 0," 661 + PreviewPrograms.COLUMN_INTERACTION_TYPE + " INTEGER," 662 + PreviewPrograms.COLUMN_INTERACTION_COUNT + " INTEGER," 663 + PreviewPrograms.COLUMN_AUTHOR + " TEXT," 664 + PreviewPrograms.COLUMN_REVIEW_RATING_STYLE + " INTEGER," 665 + PreviewPrograms.COLUMN_REVIEW_RATING + " TEXT," 666 + PreviewPrograms.COLUMN_BROWSABLE + " INTEGER NOT NULL DEFAULT 1," 667 + PreviewPrograms.COLUMN_CONTENT_ID + " TEXT," 668 + "FOREIGN KEY(" 669 + PreviewPrograms.COLUMN_CHANNEL_ID + "," + PreviewPrograms.COLUMN_PACKAGE_NAME 670 + ") REFERENCES " + CHANNELS_TABLE + "(" 671 + Channels._ID + "," + Channels.COLUMN_PACKAGE_NAME 672 + ") ON UPDATE CASCADE ON DELETE CASCADE" 673 + ");"; 674 private static final String CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL = 675 "CREATE INDEX preview_programs_package_name_index ON " + PREVIEW_PROGRAMS_TABLE 676 + "(" + PreviewPrograms.COLUMN_PACKAGE_NAME + ");"; 677 private static final String CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL = 678 "CREATE INDEX preview_programs_id_index ON " + PREVIEW_PROGRAMS_TABLE 679 + "(" + PreviewPrograms.COLUMN_CHANNEL_ID + ");"; 680 private static final String CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL = 681 "CREATE TABLE " + WATCH_NEXT_PROGRAMS_TABLE + " (" 682 + WatchNextPrograms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," 683 + WatchNextPrograms.COLUMN_PACKAGE_NAME + " TEXT NOT NULL," 684 + WatchNextPrograms.COLUMN_TITLE + " TEXT," 685 + WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER + " TEXT," 686 + WatchNextPrograms.COLUMN_SEASON_TITLE + " TEXT," 687 + WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER + " TEXT," 688 + WatchNextPrograms.COLUMN_EPISODE_TITLE + " TEXT," 689 + WatchNextPrograms.COLUMN_CANONICAL_GENRE + " TEXT," 690 + WatchNextPrograms.COLUMN_SHORT_DESCRIPTION + " TEXT," 691 + WatchNextPrograms.COLUMN_LONG_DESCRIPTION + " TEXT," 692 + WatchNextPrograms.COLUMN_VIDEO_WIDTH + " INTEGER," 693 + WatchNextPrograms.COLUMN_VIDEO_HEIGHT + " INTEGER," 694 + WatchNextPrograms.COLUMN_AUDIO_LANGUAGE + " TEXT," 695 + WatchNextPrograms.COLUMN_CONTENT_RATING + " TEXT," 696 + WatchNextPrograms.COLUMN_POSTER_ART_URI + " TEXT," 697 + WatchNextPrograms.COLUMN_THUMBNAIL_URI + " TEXT," 698 + WatchNextPrograms.COLUMN_SEARCHABLE + " INTEGER NOT NULL DEFAULT 1," 699 + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA + " BLOB," 700 + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1 + " INTEGER," 701 + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2 + " INTEGER," 702 + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3 + " INTEGER," 703 + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4 + " INTEGER," 704 + WatchNextPrograms.COLUMN_VERSION_NUMBER + " INTEGER," 705 + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID + " TEXT," 706 + WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI + " TEXT," 707 + WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS + " INTEGER," 708 + WatchNextPrograms.COLUMN_DURATION_MILLIS + " INTEGER," 709 + WatchNextPrograms.COLUMN_INTENT_URI + " TEXT," 710 + WatchNextPrograms.COLUMN_TRANSIENT + " INTEGER NOT NULL DEFAULT 0," 711 + WatchNextPrograms.COLUMN_TYPE + " INTEGER NOT NULL," 712 + WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE + " INTEGER," 713 + WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO + " INTEGER," 714 + WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO + " INTEGER," 715 + WatchNextPrograms.COLUMN_LOGO_URI + " TEXT," 716 + WatchNextPrograms.COLUMN_AVAILABILITY + " INTEGER," 717 + WatchNextPrograms.COLUMN_STARTING_PRICE + " TEXT," 718 + WatchNextPrograms.COLUMN_OFFER_PRICE + " TEXT," 719 + WatchNextPrograms.COLUMN_RELEASE_DATE + " TEXT," 720 + WatchNextPrograms.COLUMN_ITEM_COUNT + " INTEGER," 721 + WatchNextPrograms.COLUMN_LIVE + " INTEGER NOT NULL DEFAULT 0," 722 + WatchNextPrograms.COLUMN_INTERACTION_TYPE + " INTEGER," 723 + WatchNextPrograms.COLUMN_INTERACTION_COUNT + " INTEGER," 724 + WatchNextPrograms.COLUMN_AUTHOR + " TEXT," 725 + WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE + " INTEGER," 726 + WatchNextPrograms.COLUMN_REVIEW_RATING + " TEXT," 727 + WatchNextPrograms.COLUMN_BROWSABLE + " INTEGER NOT NULL DEFAULT 1," 728 + WatchNextPrograms.COLUMN_CONTENT_ID + " TEXT," 729 + WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS + " INTEGER" 730 + ");"; 731 private static final String CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL = 732 "CREATE INDEX watch_next_programs_package_name_index ON " + WATCH_NEXT_PROGRAMS_TABLE 733 + "(" + WatchNextPrograms.COLUMN_PACKAGE_NAME + ");"; 734 735 static class DatabaseHelper extends SQLiteOpenHelper { 736 private static DatabaseHelper sSingleton = null; 737 private static Context mContext; 738 getInstance(Context context)739 public static synchronized DatabaseHelper getInstance(Context context) { 740 if (sSingleton == null) { 741 sSingleton = new DatabaseHelper(context); 742 } 743 return sSingleton; 744 } 745 DatabaseHelper(Context context)746 private DatabaseHelper(Context context) { 747 this(context, DATABASE_NAME, DATABASE_VERSION); 748 } 749 750 @VisibleForTesting DatabaseHelper(Context context, String databaseName, int databaseVersion)751 DatabaseHelper(Context context, String databaseName, int databaseVersion) { 752 super(context, databaseName, null, databaseVersion); 753 mContext = context; 754 } 755 756 @Override onConfigure(SQLiteDatabase db)757 public void onConfigure(SQLiteDatabase db) { 758 db.setForeignKeyConstraintsEnabled(true); 759 } 760 761 @Override onCreate(SQLiteDatabase db)762 public void onCreate(SQLiteDatabase db) { 763 if (DEBUG) { 764 Log.d(TAG, "Creating database"); 765 } 766 // Set up the database schema. 767 db.execSQL("CREATE TABLE " + CHANNELS_TABLE + " (" 768 + Channels._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," 769 + Channels.COLUMN_PACKAGE_NAME + " TEXT NOT NULL," 770 + Channels.COLUMN_INPUT_ID + " TEXT NOT NULL," 771 + Channels.COLUMN_TYPE + " TEXT NOT NULL DEFAULT '" + Channels.TYPE_OTHER + "'," 772 + Channels.COLUMN_SERVICE_TYPE + " TEXT NOT NULL DEFAULT '" 773 + Channels.SERVICE_TYPE_AUDIO_VIDEO + "'," 774 + Channels.COLUMN_ORIGINAL_NETWORK_ID + " INTEGER NOT NULL DEFAULT 0," 775 + Channels.COLUMN_TRANSPORT_STREAM_ID + " INTEGER NOT NULL DEFAULT 0," 776 + Channels.COLUMN_SERVICE_ID + " INTEGER NOT NULL DEFAULT 0," 777 + Channels.COLUMN_DISPLAY_NUMBER + " TEXT," 778 + Channels.COLUMN_DISPLAY_NAME + " TEXT," 779 + Channels.COLUMN_NETWORK_AFFILIATION + " TEXT," 780 + Channels.COLUMN_DESCRIPTION + " TEXT," 781 + Channels.COLUMN_VIDEO_FORMAT + " TEXT," 782 + Channels.COLUMN_BROWSABLE + " INTEGER NOT NULL DEFAULT 0," 783 + Channels.COLUMN_SEARCHABLE + " INTEGER NOT NULL DEFAULT 1," 784 + Channels.COLUMN_LOCKED + " INTEGER NOT NULL DEFAULT 0," 785 + Channels.COLUMN_APP_LINK_ICON_URI + " TEXT," 786 + Channels.COLUMN_APP_LINK_POSTER_ART_URI + " TEXT," 787 + Channels.COLUMN_APP_LINK_TEXT + " TEXT," 788 + Channels.COLUMN_APP_LINK_COLOR + " INTEGER," 789 + Channels.COLUMN_APP_LINK_INTENT_URI + " TEXT," 790 + Channels.COLUMN_INTERNAL_PROVIDER_DATA + " BLOB," 791 + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + " INTEGER," 792 + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2 + " INTEGER," 793 + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3 + " INTEGER," 794 + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4 + " INTEGER," 795 + CHANNELS_COLUMN_LOGO + " BLOB," 796 + Channels.COLUMN_VERSION_NUMBER + " INTEGER," 797 + Channels.COLUMN_TRANSIENT + " INTEGER NOT NULL DEFAULT 0," 798 + Channels.COLUMN_INTERNAL_PROVIDER_ID + " TEXT," 799 // Needed for foreign keys in other tables. 800 + "UNIQUE(" + Channels._ID + "," + Channels.COLUMN_PACKAGE_NAME + ")" 801 + ");"); 802 db.execSQL("CREATE TABLE " + PROGRAMS_TABLE + " (" 803 + Programs._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," 804 + Programs.COLUMN_PACKAGE_NAME + " TEXT NOT NULL," 805 + Programs.COLUMN_CHANNEL_ID + " INTEGER," 806 + Programs.COLUMN_TITLE + " TEXT," 807 + Programs.COLUMN_SEASON_DISPLAY_NUMBER + " TEXT," 808 + Programs.COLUMN_SEASON_TITLE + " TEXT," 809 + Programs.COLUMN_EPISODE_DISPLAY_NUMBER + " TEXT," 810 + Programs.COLUMN_EPISODE_TITLE + " TEXT," 811 + Programs.COLUMN_START_TIME_UTC_MILLIS + " INTEGER," 812 + Programs.COLUMN_END_TIME_UTC_MILLIS + " INTEGER," 813 + Programs.COLUMN_BROADCAST_GENRE + " TEXT," 814 + Programs.COLUMN_CANONICAL_GENRE + " TEXT," 815 + Programs.COLUMN_SHORT_DESCRIPTION + " TEXT," 816 + Programs.COLUMN_LONG_DESCRIPTION + " TEXT," 817 + Programs.COLUMN_VIDEO_WIDTH + " INTEGER," 818 + Programs.COLUMN_VIDEO_HEIGHT + " INTEGER," 819 + Programs.COLUMN_AUDIO_LANGUAGE + " TEXT," 820 + Programs.COLUMN_CONTENT_RATING + " TEXT," 821 + Programs.COLUMN_POSTER_ART_URI + " TEXT," 822 + Programs.COLUMN_THUMBNAIL_URI + " TEXT," 823 + Programs.COLUMN_SEARCHABLE + " INTEGER NOT NULL DEFAULT 1," 824 + Programs.COLUMN_RECORDING_PROHIBITED + " INTEGER NOT NULL DEFAULT 0," 825 + Programs.COLUMN_INTERNAL_PROVIDER_DATA + " BLOB," 826 + Programs.COLUMN_INTERNAL_PROVIDER_FLAG1 + " INTEGER," 827 + Programs.COLUMN_INTERNAL_PROVIDER_FLAG2 + " INTEGER," 828 + Programs.COLUMN_INTERNAL_PROVIDER_FLAG3 + " INTEGER," 829 + Programs.COLUMN_INTERNAL_PROVIDER_FLAG4 + " INTEGER," 830 + Programs.COLUMN_REVIEW_RATING_STYLE + " INTEGER," 831 + Programs.COLUMN_REVIEW_RATING + " TEXT," 832 + Programs.COLUMN_VERSION_NUMBER + " INTEGER," 833 + "FOREIGN KEY(" 834 + Programs.COLUMN_CHANNEL_ID + "," + Programs.COLUMN_PACKAGE_NAME 835 + ") REFERENCES " + CHANNELS_TABLE + "(" 836 + Channels._ID + "," + Channels.COLUMN_PACKAGE_NAME 837 + ") ON UPDATE CASCADE ON DELETE CASCADE" 838 + ");"); 839 db.execSQL("CREATE INDEX " + PROGRAMS_TABLE_PACKAGE_NAME_INDEX + " ON " + PROGRAMS_TABLE 840 + "(" + Programs.COLUMN_PACKAGE_NAME + ");"); 841 db.execSQL("CREATE INDEX " + PROGRAMS_TABLE_CHANNEL_ID_INDEX + " ON " + PROGRAMS_TABLE 842 + "(" + Programs.COLUMN_CHANNEL_ID + ");"); 843 db.execSQL("CREATE INDEX " + PROGRAMS_TABLE_START_TIME_INDEX + " ON " + PROGRAMS_TABLE 844 + "(" + Programs.COLUMN_START_TIME_UTC_MILLIS + ");"); 845 db.execSQL("CREATE INDEX " + PROGRAMS_TABLE_END_TIME_INDEX + " ON " + PROGRAMS_TABLE 846 + "(" + Programs.COLUMN_END_TIME_UTC_MILLIS + ");"); 847 db.execSQL("CREATE TABLE " + WATCHED_PROGRAMS_TABLE + " (" 848 + WatchedPrograms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," 849 + WatchedPrograms.COLUMN_PACKAGE_NAME + " TEXT NOT NULL," 850 + WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS 851 + " INTEGER NOT NULL DEFAULT 0," 852 + WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS 853 + " INTEGER NOT NULL DEFAULT 0," 854 + WatchedPrograms.COLUMN_CHANNEL_ID + " INTEGER," 855 + WatchedPrograms.COLUMN_TITLE + " TEXT," 856 + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS + " INTEGER," 857 + WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS + " INTEGER," 858 + WatchedPrograms.COLUMN_DESCRIPTION + " TEXT," 859 + WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS + " TEXT," 860 + WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN + " TEXT NOT NULL," 861 + WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + " INTEGER NOT NULL DEFAULT 0," 862 + "FOREIGN KEY(" 863 + WatchedPrograms.COLUMN_CHANNEL_ID + "," 864 + WatchedPrograms.COLUMN_PACKAGE_NAME 865 + ") REFERENCES " + CHANNELS_TABLE + "(" 866 + Channels._ID + "," + Channels.COLUMN_PACKAGE_NAME 867 + ") ON UPDATE CASCADE ON DELETE CASCADE" 868 + ");"); 869 db.execSQL("CREATE INDEX " + WATCHED_PROGRAMS_TABLE_CHANNEL_ID_INDEX + " ON " 870 + WATCHED_PROGRAMS_TABLE + "(" + WatchedPrograms.COLUMN_CHANNEL_ID + ");"); 871 db.execSQL(CREATE_RECORDED_PROGRAMS_TABLE_SQL); 872 db.execSQL(CREATE_PREVIEW_PROGRAMS_TABLE_SQL); 873 db.execSQL(CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL); 874 db.execSQL(CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL); 875 db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL); 876 db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL); 877 } 878 879 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)880 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 881 if (oldVersion < 23) { 882 Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion 883 + ", data will be lost!"); 884 db.execSQL("DROP TABLE IF EXISTS " + DELETED_CHANNELS_TABLE); 885 db.execSQL("DROP TABLE IF EXISTS " + WATCHED_PROGRAMS_TABLE); 886 db.execSQL("DROP TABLE IF EXISTS " + PROGRAMS_TABLE); 887 db.execSQL("DROP TABLE IF EXISTS " + CHANNELS_TABLE); 888 889 onCreate(db); 890 return; 891 } 892 893 Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion + "."); 894 if (oldVersion <= 23) { 895 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 896 + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + " INTEGER;"); 897 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 898 + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2 + " INTEGER;"); 899 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 900 + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3 + " INTEGER;"); 901 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 902 + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4 + " INTEGER;"); 903 } 904 if (oldVersion <= 24) { 905 db.execSQL("ALTER TABLE " + PROGRAMS_TABLE + " ADD " 906 + Programs.COLUMN_INTERNAL_PROVIDER_FLAG1 + " INTEGER;"); 907 db.execSQL("ALTER TABLE " + PROGRAMS_TABLE + " ADD " 908 + Programs.COLUMN_INTERNAL_PROVIDER_FLAG2 + " INTEGER;"); 909 db.execSQL("ALTER TABLE " + PROGRAMS_TABLE + " ADD " 910 + Programs.COLUMN_INTERNAL_PROVIDER_FLAG3 + " INTEGER;"); 911 db.execSQL("ALTER TABLE " + PROGRAMS_TABLE + " ADD " 912 + Programs.COLUMN_INTERNAL_PROVIDER_FLAG4 + " INTEGER;"); 913 } 914 if (oldVersion <= 25) { 915 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 916 + Channels.COLUMN_APP_LINK_ICON_URI + " TEXT;"); 917 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 918 + Channels.COLUMN_APP_LINK_POSTER_ART_URI + " TEXT;"); 919 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 920 + Channels.COLUMN_APP_LINK_TEXT + " TEXT;"); 921 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 922 + Channels.COLUMN_APP_LINK_COLOR + " INTEGER;"); 923 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 924 + Channels.COLUMN_APP_LINK_INTENT_URI + " TEXT;"); 925 db.execSQL("ALTER TABLE " + PROGRAMS_TABLE + " ADD " 926 + Programs.COLUMN_SEARCHABLE + " INTEGER NOT NULL DEFAULT 1;"); 927 } 928 if (oldVersion <= 28) { 929 db.execSQL("ALTER TABLE " + PROGRAMS_TABLE + " ADD " 930 + Programs.COLUMN_SEASON_TITLE + " TEXT;"); 931 migrateIntegerColumnToTextColumn(db, PROGRAMS_TABLE, Programs.COLUMN_SEASON_NUMBER, 932 Programs.COLUMN_SEASON_DISPLAY_NUMBER); 933 migrateIntegerColumnToTextColumn(db, PROGRAMS_TABLE, Programs.COLUMN_EPISODE_NUMBER, 934 Programs.COLUMN_EPISODE_DISPLAY_NUMBER); 935 } 936 if (oldVersion <= 29) { 937 db.execSQL("DROP TABLE IF EXISTS " + RECORDED_PROGRAMS_TABLE); 938 db.execSQL(CREATE_RECORDED_PROGRAMS_TABLE_SQL); 939 } 940 if (oldVersion <= 30) { 941 db.execSQL("ALTER TABLE " + PROGRAMS_TABLE + " ADD " 942 + Programs.COLUMN_RECORDING_PROHIBITED + " INTEGER NOT NULL DEFAULT 0;"); 943 } 944 if (oldVersion <= 32) { 945 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 946 + Channels.COLUMN_TRANSIENT + " INTEGER NOT NULL DEFAULT 0;"); 947 db.execSQL("ALTER TABLE " + CHANNELS_TABLE + " ADD " 948 + Channels.COLUMN_INTERNAL_PROVIDER_ID + " TEXT;"); 949 db.execSQL("ALTER TABLE " + PROGRAMS_TABLE + " ADD " 950 + Programs.COLUMN_REVIEW_RATING_STYLE + " INTEGER;"); 951 db.execSQL("ALTER TABLE " + PROGRAMS_TABLE + " ADD " 952 + Programs.COLUMN_REVIEW_RATING + " TEXT;"); 953 if (oldVersion > 29) { 954 db.execSQL("ALTER TABLE " + RECORDED_PROGRAMS_TABLE + " ADD " 955 + RecordedPrograms.COLUMN_REVIEW_RATING_STYLE + " INTEGER;"); 956 db.execSQL("ALTER TABLE " + RECORDED_PROGRAMS_TABLE + " ADD " 957 + RecordedPrograms.COLUMN_REVIEW_RATING + " TEXT;"); 958 } 959 } 960 if (oldVersion <= 33) { 961 db.execSQL("DROP TABLE IF EXISTS " + PREVIEW_PROGRAMS_TABLE); 962 db.execSQL("DROP TABLE IF EXISTS " + WATCH_NEXT_PROGRAMS_TABLE); 963 db.execSQL(CREATE_PREVIEW_PROGRAMS_TABLE_SQL); 964 db.execSQL(CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL); 965 db.execSQL(CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL); 966 db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL); 967 db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL); 968 } 969 Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion + " is done."); 970 } 971 972 @Override onOpen(SQLiteDatabase db)973 public void onOpen(SQLiteDatabase db) { 974 // Call a static method on the TvProvider because changes to sInitialized must 975 // be guarded by a lock on the class. 976 initOnOpenIfNeeded(mContext, db); 977 } 978 migrateIntegerColumnToTextColumn(SQLiteDatabase db, String table, String integerColumn, String textColumn)979 private static void migrateIntegerColumnToTextColumn(SQLiteDatabase db, String table, 980 String integerColumn, String textColumn) { 981 db.execSQL("ALTER TABLE " + table + " ADD " + textColumn + " TEXT;"); 982 db.execSQL("UPDATE " + table + " SET " + textColumn + " = CAST(" + integerColumn 983 + " AS TEXT);"); 984 } 985 } 986 987 private DatabaseHelper mOpenHelper; 988 private static SharedPreferences sBlockedPackagesSharedPreference; 989 private static Map<String, Boolean> sBlockedPackages; 990 @VisibleForTesting 991 protected TransientRowHelper mTransientRowHelper; 992 993 private final Handler mLogHandler = new WatchLogHandler(); 994 995 @Override onCreate()996 public boolean onCreate() { 997 if (DEBUG) { 998 Log.d(TAG, "Creating TvProvider"); 999 } 1000 if (mOpenHelper == null) { 1001 mOpenHelper = DatabaseHelper.getInstance(getContext()); 1002 } 1003 mTransientRowHelper = TransientRowHelper.getInstance(getContext()); 1004 scheduleEpgDataCleanup(); 1005 buildGenreMap(); 1006 1007 // DB operation, which may trigger upgrade, should not happen in onCreate. 1008 new AsyncTask<Void, Void, Void>() { 1009 @Override 1010 protected Void doInBackground(Void... params) { 1011 deleteUnconsolidatedWatchedProgramsRows(); 1012 return null; 1013 } 1014 }.execute(); 1015 return true; 1016 } 1017 1018 @VisibleForTesting scheduleEpgDataCleanup()1019 void scheduleEpgDataCleanup() { 1020 Intent intent = new Intent(EpgDataCleanupService.ACTION_CLEAN_UP_EPG_DATA); 1021 intent.setClass(getContext(), EpgDataCleanupService.class); 1022 PendingIntent pendingIntent = PendingIntent.getService( 1023 getContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 1024 AlarmManager alarmManager = 1025 (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); 1026 alarmManager.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis(), 1027 AlarmManager.INTERVAL_HALF_DAY, pendingIntent); 1028 } 1029 buildGenreMap()1030 private void buildGenreMap() { 1031 if (sGenreMap != null) { 1032 return; 1033 } 1034 1035 sGenreMap = new HashMap<>(); 1036 buildGenreMap(R.array.genre_mapping_atsc); 1037 buildGenreMap(R.array.genre_mapping_dvb); 1038 buildGenreMap(R.array.genre_mapping_isdb); 1039 buildGenreMap(R.array.genre_mapping_isdb_br); 1040 } 1041 1042 @SuppressLint("DefaultLocale") buildGenreMap(int id)1043 private void buildGenreMap(int id) { 1044 String[] maps = getContext().getResources().getStringArray(id); 1045 for (String map : maps) { 1046 String[] arr = map.split("\\|"); 1047 if (arr.length != 2) { 1048 throw new IllegalArgumentException("Invalid genre mapping : " + map); 1049 } 1050 sGenreMap.put(arr[0].toUpperCase(), arr[1]); 1051 } 1052 } 1053 1054 @VisibleForTesting getCallingPackage_()1055 String getCallingPackage_() { 1056 return getCallingPackage(); 1057 } 1058 1059 @VisibleForTesting setOpenHelper(DatabaseHelper helper)1060 void setOpenHelper(DatabaseHelper helper) { 1061 mOpenHelper = helper; 1062 } 1063 1064 @Override getType(Uri uri)1065 public String getType(Uri uri) { 1066 switch (sUriMatcher.match(uri)) { 1067 case MATCH_CHANNEL: 1068 return Channels.CONTENT_TYPE; 1069 case MATCH_CHANNEL_ID: 1070 return Channels.CONTENT_ITEM_TYPE; 1071 case MATCH_CHANNEL_ID_LOGO: 1072 return "image/png"; 1073 case MATCH_PASSTHROUGH_ID: 1074 return Channels.CONTENT_ITEM_TYPE; 1075 case MATCH_PROGRAM: 1076 return Programs.CONTENT_TYPE; 1077 case MATCH_PROGRAM_ID: 1078 return Programs.CONTENT_ITEM_TYPE; 1079 case MATCH_WATCHED_PROGRAM: 1080 return WatchedPrograms.CONTENT_TYPE; 1081 case MATCH_WATCHED_PROGRAM_ID: 1082 return WatchedPrograms.CONTENT_ITEM_TYPE; 1083 case MATCH_RECORDED_PROGRAM: 1084 return RecordedPrograms.CONTENT_TYPE; 1085 case MATCH_RECORDED_PROGRAM_ID: 1086 return RecordedPrograms.CONTENT_ITEM_TYPE; 1087 case MATCH_PREVIEW_PROGRAM: 1088 return PreviewPrograms.CONTENT_TYPE; 1089 case MATCH_PREVIEW_PROGRAM_ID: 1090 return PreviewPrograms.CONTENT_ITEM_TYPE; 1091 case MATCH_WATCH_NEXT_PROGRAM: 1092 return WatchNextPrograms.CONTENT_TYPE; 1093 case MATCH_WATCH_NEXT_PROGRAM_ID: 1094 return WatchNextPrograms.CONTENT_ITEM_TYPE; 1095 default: 1096 throw new IllegalArgumentException("Unknown URI " + uri); 1097 } 1098 } 1099 1100 @Override call(String method, String arg, Bundle extras)1101 public Bundle call(String method, String arg, Bundle extras) { 1102 if (!callerHasAccessAllEpgDataPermission()) { 1103 return null; 1104 } 1105 ensureInitialized(); 1106 Map<String, String> projectionMap; 1107 switch (method) { 1108 case TvContract.METHOD_GET_COLUMNS: 1109 switch (sUriMatcher.match(Uri.parse(arg))) { 1110 case MATCH_CHANNEL: 1111 projectionMap = sChannelProjectionMap; 1112 break; 1113 case MATCH_PROGRAM: 1114 projectionMap = sProgramProjectionMap; 1115 break; 1116 case MATCH_PREVIEW_PROGRAM: 1117 projectionMap = sPreviewProgramProjectionMap; 1118 break; 1119 case MATCH_WATCH_NEXT_PROGRAM: 1120 projectionMap = sWatchNextProgramProjectionMap; 1121 break; 1122 case MATCH_RECORDED_PROGRAM: 1123 projectionMap = sRecordedProgramProjectionMap; 1124 break; 1125 default: 1126 return null; 1127 } 1128 Bundle result = new Bundle(); 1129 result.putStringArray(TvContract.EXTRA_EXISTING_COLUMN_NAMES, 1130 projectionMap.keySet().toArray(new String[projectionMap.size()])); 1131 return result; 1132 case TvContract.METHOD_ADD_COLUMN: 1133 CharSequence columnName = extras.getCharSequence(TvContract.EXTRA_COLUMN_NAME); 1134 CharSequence dataType = extras.getCharSequence(TvContract.EXTRA_DATA_TYPE); 1135 if (TextUtils.isEmpty(columnName) || TextUtils.isEmpty(dataType)) { 1136 return null; 1137 } 1138 CharSequence defaultValue = extras.getCharSequence(TvContract.EXTRA_DEFAULT_VALUE); 1139 try { 1140 defaultValue = TextUtils.isEmpty(defaultValue) ? "" : generateDefaultClause( 1141 dataType.toString(), defaultValue.toString()); 1142 } catch (IllegalArgumentException e) { 1143 return null; 1144 } 1145 String tableName; 1146 switch (sUriMatcher.match(Uri.parse(arg))) { 1147 case MATCH_CHANNEL: 1148 tableName = CHANNELS_TABLE; 1149 projectionMap = sChannelProjectionMap; 1150 break; 1151 case MATCH_PROGRAM: 1152 tableName = PROGRAMS_TABLE; 1153 projectionMap = sProgramProjectionMap; 1154 break; 1155 case MATCH_PREVIEW_PROGRAM: 1156 tableName = PREVIEW_PROGRAMS_TABLE; 1157 projectionMap = sPreviewProgramProjectionMap; 1158 break; 1159 case MATCH_WATCH_NEXT_PROGRAM: 1160 tableName = WATCH_NEXT_PROGRAMS_TABLE; 1161 projectionMap = sWatchNextProgramProjectionMap; 1162 break; 1163 case MATCH_RECORDED_PROGRAM: 1164 tableName = RECORDED_PROGRAMS_TABLE; 1165 projectionMap = sRecordedProgramProjectionMap; 1166 break; 1167 default: 1168 return null; 1169 } 1170 try (SQLiteDatabase db = mOpenHelper.getWritableDatabase()) { 1171 db.execSQL("ALTER TABLE " + tableName + " ADD " 1172 + columnName + " " + dataType + defaultValue + ";"); 1173 projectionMap.put(columnName.toString(), tableName + '.' + columnName); 1174 Bundle returnValue = new Bundle(); 1175 returnValue.putStringArray(TvContract.EXTRA_EXISTING_COLUMN_NAMES, 1176 projectionMap.keySet().toArray(new String[projectionMap.size()])); 1177 return returnValue; 1178 } catch (SQLException e) { 1179 return null; 1180 } 1181 case TvContract.METHOD_GET_BLOCKED_PACKAGES: 1182 Bundle allBlockedPackages = new Bundle(); 1183 allBlockedPackages.putStringArray(TvContract.EXTRA_BLOCKED_PACKAGES, 1184 sBlockedPackages.keySet().toArray(new String[sBlockedPackages.size()])); 1185 return allBlockedPackages; 1186 case TvContract.METHOD_BLOCK_PACKAGE: 1187 String packageNameToBlock = arg; 1188 Bundle blockPackageResult = new Bundle(); 1189 if (!TextUtils.isEmpty(packageNameToBlock)) { 1190 sBlockedPackages.put(packageNameToBlock, true); 1191 if (sBlockedPackagesSharedPreference.edit().putStringSet( 1192 SHARED_PREF_BLOCKED_PACKAGES_KEY, sBlockedPackages.keySet()).commit()) { 1193 String[] channelSelectionArgs = new String[] { 1194 packageNameToBlock, Channels.TYPE_PREVIEW }; 1195 delete(TvContract.Channels.CONTENT_URI, 1196 Channels.COLUMN_PACKAGE_NAME + "=? AND " 1197 + Channels.COLUMN_TYPE + "=?", 1198 channelSelectionArgs); 1199 String[] programsSelectionArgs = new String[] { 1200 packageNameToBlock }; 1201 delete(TvContract.PreviewPrograms.CONTENT_URI, 1202 PreviewPrograms.COLUMN_PACKAGE_NAME + "=?", programsSelectionArgs); 1203 delete(TvContract.WatchNextPrograms.CONTENT_URI, 1204 WatchNextPrograms.COLUMN_PACKAGE_NAME + "=?", 1205 programsSelectionArgs); 1206 blockPackageResult.putInt( 1207 TvContract.EXTRA_RESULT_CODE, TvContract.RESULT_OK); 1208 } else { 1209 Log.e(TAG, "Blocking package " + packageNameToBlock + " failed"); 1210 sBlockedPackages.remove(packageNameToBlock); 1211 blockPackageResult.putInt(TvContract.EXTRA_RESULT_CODE, TvContract.RESULT_ERROR_IO); 1212 } 1213 } else { 1214 blockPackageResult.putInt( 1215 TvContract.EXTRA_RESULT_CODE, TvContract.RESULT_ERROR_INVALID_ARGUMENT); 1216 } 1217 return blockPackageResult; 1218 case TvContract.METHOD_UNBLOCK_PACKAGE: 1219 String packageNameToUnblock = arg; 1220 Bundle unblockPackageResult = new Bundle(); 1221 if (!TextUtils.isEmpty(packageNameToUnblock)) { 1222 sBlockedPackages.remove(packageNameToUnblock); 1223 if (sBlockedPackagesSharedPreference.edit().putStringSet( 1224 SHARED_PREF_BLOCKED_PACKAGES_KEY, sBlockedPackages.keySet()).commit()) { 1225 unblockPackageResult.putInt( 1226 TvContract.EXTRA_RESULT_CODE, TvContract.RESULT_OK); 1227 } else { 1228 Log.e(TAG, "Unblocking package " + packageNameToUnblock + " failed"); 1229 sBlockedPackages.put(packageNameToUnblock, true); 1230 unblockPackageResult.putInt( 1231 TvContract.EXTRA_RESULT_CODE, TvContract.RESULT_ERROR_IO); 1232 } 1233 } else { 1234 unblockPackageResult.putInt( 1235 TvContract.EXTRA_RESULT_CODE, TvContract.RESULT_ERROR_INVALID_ARGUMENT); 1236 } 1237 return unblockPackageResult; 1238 } 1239 return null; 1240 } 1241 1242 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)1243 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 1244 String sortOrder) { 1245 ensureInitialized(); 1246 mTransientRowHelper.ensureOldTransientRowsDeleted(); 1247 boolean needsToValidateSortOrder = !callerHasAccessAllEpgDataPermission(); 1248 SqlParams params = createSqlParams(OP_QUERY, uri, selection, selectionArgs); 1249 1250 SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 1251 queryBuilder.setStrict(needsToValidateSortOrder); 1252 queryBuilder.setTables(params.getTables()); 1253 String orderBy = null; 1254 Map<String, String> projectionMap; 1255 switch (params.getTables()) { 1256 case PROGRAMS_TABLE: 1257 projectionMap = sProgramProjectionMap; 1258 orderBy = DEFAULT_PROGRAMS_SORT_ORDER; 1259 break; 1260 case WATCHED_PROGRAMS_TABLE: 1261 projectionMap = sWatchedProgramProjectionMap; 1262 orderBy = DEFAULT_WATCHED_PROGRAMS_SORT_ORDER; 1263 break; 1264 case RECORDED_PROGRAMS_TABLE: 1265 projectionMap = sRecordedProgramProjectionMap; 1266 break; 1267 case PREVIEW_PROGRAMS_TABLE: 1268 projectionMap = sPreviewProgramProjectionMap; 1269 break; 1270 case WATCH_NEXT_PROGRAMS_TABLE: 1271 projectionMap = sWatchNextProgramProjectionMap; 1272 break; 1273 default: 1274 projectionMap = sChannelProjectionMap; 1275 break; 1276 } 1277 queryBuilder.setProjectionMap(createProjectionMapForQuery(projection, projectionMap)); 1278 if (needsToValidateSortOrder) { 1279 validateSortOrder(sortOrder, projectionMap.keySet()); 1280 } 1281 1282 // Use the default sort order only if no sort order is specified. 1283 if (!TextUtils.isEmpty(sortOrder)) { 1284 orderBy = sortOrder; 1285 } 1286 1287 // Get the database and run the query. 1288 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 1289 Cursor c = queryBuilder.query(db, projection, params.getSelection(), 1290 params.getSelectionArgs(), null, null, orderBy); 1291 1292 // Tell the cursor what URI to watch, so it knows when its source data changes. 1293 c.setNotificationUri(getContext().getContentResolver(), uri); 1294 return c; 1295 } 1296 1297 @Override insert(Uri uri, ContentValues values)1298 public Uri insert(Uri uri, ContentValues values) { 1299 ensureInitialized(); 1300 mTransientRowHelper.ensureOldTransientRowsDeleted(); 1301 switch (sUriMatcher.match(uri)) { 1302 case MATCH_CHANNEL: 1303 // Preview channels are not necessarily associated with TV input service. 1304 // Therefore, we fill a fake ID to meet not null restriction for preview channels. 1305 if (values.get(Channels.COLUMN_INPUT_ID) == null 1306 && Channels.TYPE_PREVIEW.equals(values.get(Channels.COLUMN_TYPE))) { 1307 values.put(Channels.COLUMN_INPUT_ID, EMPTY_STRING); 1308 } 1309 filterContentValues(values, sChannelProjectionMap); 1310 return insertChannel(uri, values); 1311 case MATCH_PROGRAM: 1312 filterContentValues(values, sProgramProjectionMap); 1313 return insertProgram(uri, values); 1314 case MATCH_WATCHED_PROGRAM: 1315 return insertWatchedProgram(uri, values); 1316 case MATCH_RECORDED_PROGRAM: 1317 filterContentValues(values, sRecordedProgramProjectionMap); 1318 return insertRecordedProgram(uri, values); 1319 case MATCH_PREVIEW_PROGRAM: 1320 filterContentValues(values, sPreviewProgramProjectionMap); 1321 return insertPreviewProgram(uri, values); 1322 case MATCH_WATCH_NEXT_PROGRAM: 1323 filterContentValues(values, sWatchNextProgramProjectionMap); 1324 return insertWatchNextProgram(uri, values); 1325 case MATCH_CHANNEL_ID: 1326 case MATCH_CHANNEL_ID_LOGO: 1327 case MATCH_PASSTHROUGH_ID: 1328 case MATCH_PROGRAM_ID: 1329 case MATCH_WATCHED_PROGRAM_ID: 1330 case MATCH_RECORDED_PROGRAM_ID: 1331 case MATCH_PREVIEW_PROGRAM_ID: 1332 throw new UnsupportedOperationException("Cannot insert into that URI: " + uri); 1333 default: 1334 throw new IllegalArgumentException("Unknown URI " + uri); 1335 } 1336 } 1337 insertChannel(Uri uri, ContentValues values)1338 private Uri insertChannel(Uri uri, ContentValues values) { 1339 if (TextUtils.equals(values.getAsString(Channels.COLUMN_TYPE), Channels.TYPE_PREVIEW)) { 1340 blockIllegalAccessFromBlockedPackage(); 1341 } 1342 // Mark the owner package of this channel. 1343 values.put(Channels.COLUMN_PACKAGE_NAME, getCallingPackage_()); 1344 blockIllegalAccessToChannelsSystemColumns(values); 1345 1346 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1347 long rowId = db.insert(CHANNELS_TABLE, null, values); 1348 if (rowId > 0) { 1349 Uri channelUri = TvContract.buildChannelUri(rowId); 1350 notifyChange(channelUri); 1351 return channelUri; 1352 } 1353 1354 throw new SQLException("Failed to insert row into " + uri); 1355 } 1356 insertProgram(Uri uri, ContentValues values)1357 private Uri insertProgram(Uri uri, ContentValues values) { 1358 if (!callerHasAccessAllEpgDataPermission() || 1359 !values.containsKey(Programs.COLUMN_PACKAGE_NAME)) { 1360 // Mark the owner package of this program. System app with a proper permission may 1361 // change the owner of the program. 1362 values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_()); 1363 } 1364 1365 checkAndConvertGenre(values); 1366 checkAndConvertDeprecatedColumns(values); 1367 1368 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1369 long rowId = db.insert(PROGRAMS_TABLE, null, values); 1370 if (rowId > 0) { 1371 Uri programUri = TvContract.buildProgramUri(rowId); 1372 notifyChange(programUri); 1373 return programUri; 1374 } 1375 1376 throw new SQLException("Failed to insert row into " + uri); 1377 } 1378 insertWatchedProgram(Uri uri, ContentValues values)1379 private Uri insertWatchedProgram(Uri uri, ContentValues values) { 1380 if (DEBUG) { 1381 Log.d(TAG, "insertWatchedProgram(uri=" + uri + ", values={" + values + "})"); 1382 } 1383 Long watchStartTime = values.getAsLong(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS); 1384 Long watchEndTime = values.getAsLong(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS); 1385 // The system sends only two kinds of watch events: 1386 // 1. The user tunes to a new channel. (COLUMN_WATCH_START_TIME_UTC_MILLIS) 1387 // 2. The user stops watching. (COLUMN_WATCH_END_TIME_UTC_MILLIS) 1388 if (watchStartTime != null && watchEndTime == null) { 1389 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1390 long rowId = db.insert(WATCHED_PROGRAMS_TABLE, null, values); 1391 if (rowId > 0) { 1392 mLogHandler.removeMessages(WatchLogHandler.MSG_TRY_CONSOLIDATE_ALL); 1393 mLogHandler.sendEmptyMessageDelayed(WatchLogHandler.MSG_TRY_CONSOLIDATE_ALL, 1394 MAX_PROGRAM_DATA_DELAY_IN_MILLIS); 1395 return TvContract.buildWatchedProgramUri(rowId); 1396 } 1397 Log.w(TAG, "Failed to insert row for " + values + ". Channel does not exist."); 1398 return null; 1399 } else if (watchStartTime == null && watchEndTime != null) { 1400 SomeArgs args = SomeArgs.obtain(); 1401 args.arg1 = values.getAsString(WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN); 1402 args.arg2 = watchEndTime; 1403 Message msg = mLogHandler.obtainMessage(WatchLogHandler.MSG_CONSOLIDATE, args); 1404 mLogHandler.sendMessageDelayed(msg, MAX_PROGRAM_DATA_DELAY_IN_MILLIS); 1405 return null; 1406 } 1407 // All the other cases are invalid. 1408 throw new IllegalArgumentException("Only one of COLUMN_WATCH_START_TIME_UTC_MILLIS and" 1409 + " COLUMN_WATCH_END_TIME_UTC_MILLIS should be specified"); 1410 } 1411 insertRecordedProgram(Uri uri, ContentValues values)1412 private Uri insertRecordedProgram(Uri uri, ContentValues values) { 1413 // Mark the owner package of this program. 1414 values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_()); 1415 1416 checkAndConvertGenre(values); 1417 1418 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1419 long rowId = db.insert(RECORDED_PROGRAMS_TABLE, null, values); 1420 if (rowId > 0) { 1421 Uri recordedProgramUri = TvContract.buildRecordedProgramUri(rowId); 1422 notifyChange(recordedProgramUri); 1423 return recordedProgramUri; 1424 } 1425 1426 throw new SQLException("Failed to insert row into " + uri); 1427 } 1428 insertPreviewProgram(Uri uri, ContentValues values)1429 private Uri insertPreviewProgram(Uri uri, ContentValues values) { 1430 if (!values.containsKey(PreviewPrograms.COLUMN_TYPE)) { 1431 throw new IllegalArgumentException("Missing the required column: " + 1432 PreviewPrograms.COLUMN_TYPE); 1433 } 1434 blockIllegalAccessFromBlockedPackage(); 1435 // Mark the owner package of this program. 1436 values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_()); 1437 blockIllegalAccessToPreviewProgramsSystemColumns(values); 1438 1439 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1440 long rowId = db.insert(PREVIEW_PROGRAMS_TABLE, null, values); 1441 if (rowId > 0) { 1442 Uri previewProgramUri = TvContract.buildPreviewProgramUri(rowId); 1443 notifyChange(previewProgramUri); 1444 return previewProgramUri; 1445 } 1446 1447 throw new SQLException("Failed to insert row into " + uri); 1448 } 1449 insertWatchNextProgram(Uri uri, ContentValues values)1450 private Uri insertWatchNextProgram(Uri uri, ContentValues values) { 1451 if (!values.containsKey(WatchNextPrograms.COLUMN_TYPE)) { 1452 throw new IllegalArgumentException("Missing the required column: " + 1453 WatchNextPrograms.COLUMN_TYPE); 1454 } 1455 blockIllegalAccessFromBlockedPackage(); 1456 if (!callerHasAccessAllEpgDataPermission() || 1457 !values.containsKey(Programs.COLUMN_PACKAGE_NAME)) { 1458 // Mark the owner package of this program. System app with a proper permission may 1459 // change the owner of the program. 1460 values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_()); 1461 } 1462 blockIllegalAccessToPreviewProgramsSystemColumns(values); 1463 1464 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1465 long rowId = db.insert(WATCH_NEXT_PROGRAMS_TABLE, null, values); 1466 if (rowId > 0) { 1467 Uri watchNextProgramUri = TvContract.buildWatchNextProgramUri(rowId); 1468 notifyChange(watchNextProgramUri); 1469 return watchNextProgramUri; 1470 } 1471 1472 throw new SQLException("Failed to insert row into " + uri); 1473 } 1474 1475 @Override delete(Uri uri, String selection, String[] selectionArgs)1476 public int delete(Uri uri, String selection, String[] selectionArgs) { 1477 mTransientRowHelper.ensureOldTransientRowsDeleted(); 1478 SqlParams params = createSqlParams(OP_DELETE, uri, selection, selectionArgs); 1479 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1480 int count; 1481 switch (sUriMatcher.match(uri)) { 1482 case MATCH_CHANNEL_ID_LOGO: 1483 ContentValues values = new ContentValues(); 1484 values.putNull(CHANNELS_COLUMN_LOGO); 1485 count = db.update(params.getTables(), values, params.getSelection(), 1486 params.getSelectionArgs()); 1487 break; 1488 case MATCH_CHANNEL: 1489 case MATCH_PROGRAM: 1490 case MATCH_WATCHED_PROGRAM: 1491 case MATCH_RECORDED_PROGRAM: 1492 case MATCH_PREVIEW_PROGRAM: 1493 case MATCH_WATCH_NEXT_PROGRAM: 1494 case MATCH_CHANNEL_ID: 1495 case MATCH_PASSTHROUGH_ID: 1496 case MATCH_PROGRAM_ID: 1497 case MATCH_WATCHED_PROGRAM_ID: 1498 case MATCH_RECORDED_PROGRAM_ID: 1499 case MATCH_PREVIEW_PROGRAM_ID: 1500 case MATCH_WATCH_NEXT_PROGRAM_ID: 1501 count = db.delete(params.getTables(), params.getSelection(), 1502 params.getSelectionArgs()); 1503 break; 1504 default: 1505 throw new IllegalArgumentException("Unknown URI " + uri); 1506 } 1507 if (count > 0) { 1508 notifyChange(uri); 1509 } 1510 return count; 1511 } 1512 1513 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)1514 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 1515 mTransientRowHelper.ensureOldTransientRowsDeleted(); 1516 SqlParams params = createSqlParams(OP_UPDATE, uri, selection, selectionArgs); 1517 blockIllegalAccessToIdAndPackageName(uri, values); 1518 boolean containImmutableColumn = false; 1519 if (params.getTables().equals(CHANNELS_TABLE)) { 1520 filterContentValues(values, sChannelProjectionMap); 1521 containImmutableColumn = disallowModifyChannelType(values, params); 1522 if (containImmutableColumn && sUriMatcher.match(uri) != MATCH_CHANNEL_ID) { 1523 Log.i(TAG, "Updating failed. Attempt to change immutable column for channels."); 1524 return 0; 1525 } 1526 blockIllegalAccessToChannelsSystemColumns(values); 1527 } else if (params.getTables().equals(PROGRAMS_TABLE)) { 1528 filterContentValues(values, sProgramProjectionMap); 1529 checkAndConvertGenre(values); 1530 checkAndConvertDeprecatedColumns(values); 1531 } else if (params.getTables().equals(RECORDED_PROGRAMS_TABLE)) { 1532 filterContentValues(values, sRecordedProgramProjectionMap); 1533 checkAndConvertGenre(values); 1534 } else if (params.getTables().equals(PREVIEW_PROGRAMS_TABLE)) { 1535 filterContentValues(values, sPreviewProgramProjectionMap); 1536 containImmutableColumn = disallowModifyChannelId(values, params); 1537 if (containImmutableColumn && PreviewPrograms.CONTENT_URI.equals(uri)) { 1538 Log.i(TAG, "Updating failed. Attempt to change unmodifiable column for " 1539 + "preview programs."); 1540 return 0; 1541 } 1542 blockIllegalAccessToPreviewProgramsSystemColumns(values); 1543 } else if (params.getTables().equals(WATCH_NEXT_PROGRAMS_TABLE)) { 1544 filterContentValues(values, sWatchNextProgramProjectionMap); 1545 blockIllegalAccessToPreviewProgramsSystemColumns(values); 1546 } 1547 if (values.size() == 0) { 1548 // All values may be filtered out, no need to update 1549 return 0; 1550 } 1551 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1552 int count = db.update(params.getTables(), values, params.getSelection(), 1553 params.getSelectionArgs()); 1554 if (count > 0) { 1555 notifyChange(uri); 1556 } else if (containImmutableColumn) { 1557 Log.i(TAG, "Updating failed. The item may not exist or attempt to change " 1558 + "immutable column."); 1559 } 1560 return count; 1561 } 1562 ensureInitialized()1563 private synchronized void ensureInitialized() { 1564 if (!sInitialized) { 1565 // Database is not accessed before and the projection maps and the blocked package list 1566 // are not updated yet. Gets database here to make it initialized. 1567 mOpenHelper.getReadableDatabase(); 1568 } 1569 } 1570 initOnOpenIfNeeded(Context context, SQLiteDatabase db)1571 private static synchronized void initOnOpenIfNeeded(Context context, SQLiteDatabase db) { 1572 if (!sInitialized) { 1573 updateProjectionMap(db, CHANNELS_TABLE, sChannelProjectionMap); 1574 updateProjectionMap(db, PROGRAMS_TABLE, sProgramProjectionMap); 1575 updateProjectionMap(db, WATCHED_PROGRAMS_TABLE, sWatchedProgramProjectionMap); 1576 updateProjectionMap(db, RECORDED_PROGRAMS_TABLE, sRecordedProgramProjectionMap); 1577 updateProjectionMap(db, PREVIEW_PROGRAMS_TABLE, sPreviewProgramProjectionMap); 1578 updateProjectionMap(db, WATCH_NEXT_PROGRAMS_TABLE, sWatchNextProgramProjectionMap); 1579 sBlockedPackagesSharedPreference = PreferenceManager.getDefaultSharedPreferences( 1580 context); 1581 sBlockedPackages = new ConcurrentHashMap<>(); 1582 for (String packageName : sBlockedPackagesSharedPreference.getStringSet( 1583 SHARED_PREF_BLOCKED_PACKAGES_KEY, new HashSet<>())) { 1584 sBlockedPackages.put(packageName, true); 1585 } 1586 sInitialized = true; 1587 } 1588 } 1589 updateProjectionMap(SQLiteDatabase db, String tableName, Map<String, String> projectionMap)1590 private static void updateProjectionMap(SQLiteDatabase db, String tableName, 1591 Map<String, String> projectionMap) { 1592 try(Cursor cursor = db.rawQuery("SELECT * FROM " + tableName + " LIMIT 0", null)) { 1593 for (String columnName : cursor.getColumnNames()) { 1594 if (!projectionMap.containsKey(columnName)) { 1595 projectionMap.put(columnName, tableName + '.' + columnName); 1596 } 1597 } 1598 } 1599 } 1600 createProjectionMapForQuery(String[] projection, Map<String, String> projectionMap)1601 private Map<String, String> createProjectionMapForQuery(String[] projection, 1602 Map<String, String> projectionMap) { 1603 if (projection == null) { 1604 return projectionMap; 1605 } 1606 Map<String, String> columnProjectionMap = new HashMap<>(); 1607 for (String columnName : projection) { 1608 String value = projectionMap.get(columnName); 1609 if (value != null) { 1610 columnProjectionMap.put(columnName, value); 1611 } else { 1612 // Value NULL will be provided if the requested column does not exist in the 1613 // database. 1614 value = "NULL AS " + DatabaseUtils.sqlEscapeString(columnName); 1615 columnProjectionMap.put(columnName, value); 1616 1617 if (needEventLog(columnName)) { 1618 android.util.EventLog.writeEvent(0x534e4554, "135269669", -1, ""); 1619 } 1620 } 1621 } 1622 return columnProjectionMap; 1623 } 1624 needEventLog(String columnName)1625 private boolean needEventLog(String columnName) { 1626 for (int i = 0; i < columnName.length(); i++) { 1627 char c = columnName.charAt(i); 1628 if (!Character.isLetterOrDigit(c) && c != '_') { 1629 return true; 1630 } 1631 } 1632 return false; 1633 } 1634 filterContentValues(ContentValues values, Map<String, String> projectionMap)1635 private void filterContentValues(ContentValues values, Map<String, String> projectionMap) { 1636 Iterator<String> iter = values.keySet().iterator(); 1637 while (iter.hasNext()) { 1638 String columnName = iter.next(); 1639 if (!projectionMap.containsKey(columnName)) { 1640 iter.remove(); 1641 } 1642 } 1643 } 1644 createSqlParams(String operation, Uri uri, String selection, String[] selectionArgs)1645 private SqlParams createSqlParams(String operation, Uri uri, String selection, 1646 String[] selectionArgs) { 1647 int match = sUriMatcher.match(uri); 1648 1649 SqliteTokenFinder.findTokens(selection, p -> { 1650 if (p.first == SqliteTokenFinder.TYPE_REGULAR 1651 && TextUtils.equals(p.second.toUpperCase(Locale.US), "SELECT")) { 1652 // only when a keyword is not in quotes or brackets 1653 // see https://www.sqlite.org/lang_keywords.html 1654 android.util.EventLog.writeEvent(0x534e4554, "135269669", -1, ""); 1655 throw new SecurityException( 1656 "Subquery is not allowed in selection: " + selection); 1657 } 1658 }); 1659 1660 SqlParams params = new SqlParams(null, selection, selectionArgs); 1661 1662 // Control access to EPG data (excluding watched programs) when the caller doesn't have all 1663 // access. 1664 String prefix = match == MATCH_CHANNEL ? CHANNELS_TABLE + "." : ""; 1665 if (!callerHasAccessAllEpgDataPermission() 1666 && match != MATCH_WATCHED_PROGRAM && match != MATCH_WATCHED_PROGRAM_ID) { 1667 if (!TextUtils.isEmpty(selection)) { 1668 throw new SecurityException("Selection not allowed for " + uri); 1669 } 1670 // Limit the operation only to the data that the calling package owns except for when 1671 // the caller tries to read TV listings and has the appropriate permission. 1672 if (operation.equals(OP_QUERY) && callerHasReadTvListingsPermission()) { 1673 params.setWhere(prefix + BaseTvColumns.COLUMN_PACKAGE_NAME + "=? OR " 1674 + Channels.COLUMN_SEARCHABLE + "=?", getCallingPackage_(), "1"); 1675 } else { 1676 params.setWhere(prefix + BaseTvColumns.COLUMN_PACKAGE_NAME + "=?", 1677 getCallingPackage_()); 1678 } 1679 } 1680 String packageName = uri.getQueryParameter(TvContract.PARAM_PACKAGE); 1681 if (packageName != null) { 1682 params.appendWhere(prefix + BaseTvColumns.COLUMN_PACKAGE_NAME + "=?", packageName); 1683 } 1684 1685 switch (match) { 1686 case MATCH_CHANNEL: 1687 String genre = uri.getQueryParameter(TvContract.PARAM_CANONICAL_GENRE); 1688 if (genre == null) { 1689 params.setTables(CHANNELS_TABLE); 1690 } else { 1691 if (!operation.equals(OP_QUERY)) { 1692 throw new SecurityException(capitalize(operation) 1693 + " not allowed for " + uri); 1694 } 1695 if (!Genres.isCanonical(genre)) { 1696 throw new IllegalArgumentException("Not a canonical genre : " + genre); 1697 } 1698 params.setTables(CHANNELS_TABLE_INNER_JOIN_PROGRAMS_TABLE); 1699 String curTime = String.valueOf(System.currentTimeMillis()); 1700 params.appendWhere("LIKE(?, " + Programs.COLUMN_CANONICAL_GENRE + ") AND " 1701 + Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND " 1702 + Programs.COLUMN_END_TIME_UTC_MILLIS + ">=?", 1703 "%" + genre + "%", curTime, curTime); 1704 } 1705 String inputId = uri.getQueryParameter(TvContract.PARAM_INPUT); 1706 if (inputId != null) { 1707 params.appendWhere(Channels.COLUMN_INPUT_ID + "=?", inputId); 1708 } 1709 boolean browsableOnly = uri.getBooleanQueryParameter( 1710 TvContract.PARAM_BROWSABLE_ONLY, false); 1711 if (browsableOnly) { 1712 params.appendWhere(Channels.COLUMN_BROWSABLE + "=1"); 1713 } 1714 String preview = uri.getQueryParameter(TvContract.PARAM_PREVIEW); 1715 if (preview != null) { 1716 String previewSelection = Channels.COLUMN_TYPE 1717 + (preview.equals(String.valueOf(true)) ? "=?" : "!=?"); 1718 params.appendWhere(previewSelection, Channels.TYPE_PREVIEW); 1719 } 1720 break; 1721 case MATCH_CHANNEL_ID: 1722 params.setTables(CHANNELS_TABLE); 1723 params.appendWhere(Channels._ID + "=?", uri.getLastPathSegment()); 1724 break; 1725 case MATCH_PROGRAM: 1726 params.setTables(PROGRAMS_TABLE); 1727 String paramChannelId = uri.getQueryParameter(TvContract.PARAM_CHANNEL); 1728 if (paramChannelId != null) { 1729 String channelId = String.valueOf(Long.parseLong(paramChannelId)); 1730 params.appendWhere(Programs.COLUMN_CHANNEL_ID + "=?", channelId); 1731 } 1732 String paramStartTime = uri.getQueryParameter(TvContract.PARAM_START_TIME); 1733 String paramEndTime = uri.getQueryParameter(TvContract.PARAM_END_TIME); 1734 if (paramStartTime != null && paramEndTime != null) { 1735 String startTime = String.valueOf(Long.parseLong(paramStartTime)); 1736 String endTime = String.valueOf(Long.parseLong(paramEndTime)); 1737 params.appendWhere(Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND " 1738 + Programs.COLUMN_END_TIME_UTC_MILLIS + ">=? AND ?<=?", endTime, 1739 startTime, startTime, endTime); 1740 } 1741 break; 1742 case MATCH_PROGRAM_ID: 1743 params.setTables(PROGRAMS_TABLE); 1744 params.appendWhere(Programs._ID + "=?", uri.getLastPathSegment()); 1745 break; 1746 case MATCH_WATCHED_PROGRAM: 1747 if (!callerHasAccessWatchedProgramsPermission()) { 1748 throw new SecurityException("Access not allowed for " + uri); 1749 } 1750 params.setTables(WATCHED_PROGRAMS_TABLE); 1751 params.appendWhere(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=?", "1"); 1752 break; 1753 case MATCH_WATCHED_PROGRAM_ID: 1754 if (!callerHasAccessWatchedProgramsPermission()) { 1755 throw new SecurityException("Access not allowed for " + uri); 1756 } 1757 params.setTables(WATCHED_PROGRAMS_TABLE); 1758 params.appendWhere(WatchedPrograms._ID + "=?", uri.getLastPathSegment()); 1759 params.appendWhere(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=?", "1"); 1760 break; 1761 case MATCH_RECORDED_PROGRAM_ID: 1762 params.appendWhere(RecordedPrograms._ID + "=?", uri.getLastPathSegment()); 1763 // fall-through 1764 case MATCH_RECORDED_PROGRAM: 1765 params.setTables(RECORDED_PROGRAMS_TABLE); 1766 paramChannelId = uri.getQueryParameter(TvContract.PARAM_CHANNEL); 1767 if (paramChannelId != null) { 1768 String channelId = String.valueOf(Long.parseLong(paramChannelId)); 1769 params.appendWhere(Programs.COLUMN_CHANNEL_ID + "=?", channelId); 1770 } 1771 break; 1772 case MATCH_PREVIEW_PROGRAM_ID: 1773 params.appendWhere(PreviewPrograms._ID + "=?", uri.getLastPathSegment()); 1774 // fall-through 1775 case MATCH_PREVIEW_PROGRAM: 1776 params.setTables(PREVIEW_PROGRAMS_TABLE); 1777 paramChannelId = uri.getQueryParameter(TvContract.PARAM_CHANNEL); 1778 if (paramChannelId != null) { 1779 String channelId = String.valueOf(Long.parseLong(paramChannelId)); 1780 params.appendWhere(PreviewPrograms.COLUMN_CHANNEL_ID + "=?", channelId); 1781 } 1782 break; 1783 case MATCH_WATCH_NEXT_PROGRAM_ID: 1784 params.appendWhere(WatchNextPrograms._ID + "=?", uri.getLastPathSegment()); 1785 // fall-through 1786 case MATCH_WATCH_NEXT_PROGRAM: 1787 params.setTables(WATCH_NEXT_PROGRAMS_TABLE); 1788 break; 1789 case MATCH_CHANNEL_ID_LOGO: 1790 if (operation.equals(OP_DELETE)) { 1791 params.setTables(CHANNELS_TABLE); 1792 params.appendWhere(Channels._ID + "=?", uri.getPathSegments().get(1)); 1793 break; 1794 } 1795 // fall-through 1796 case MATCH_PASSTHROUGH_ID: 1797 throw new UnsupportedOperationException(operation + " not permmitted on " + uri); 1798 default: 1799 throw new IllegalArgumentException("Unknown URI " + uri); 1800 } 1801 return params; 1802 } 1803 generateDefaultClause(String dataType, String defaultValue)1804 private static String generateDefaultClause(String dataType, String defaultValue) 1805 throws IllegalArgumentException { 1806 String defaultValueString = " DEFAULT "; 1807 switch (dataType.toLowerCase()) { 1808 case "integer": 1809 return defaultValueString + Integer.parseInt(defaultValue); 1810 case "real": 1811 return defaultValueString + Double.parseDouble(defaultValue); 1812 case "text": 1813 case "blob": 1814 return defaultValueString + DatabaseUtils.sqlEscapeString(defaultValue); 1815 default: 1816 throw new IllegalArgumentException("Illegal data type \"" + dataType 1817 + "\" with default value: " + defaultValue); 1818 } 1819 } 1820 capitalize(String str)1821 private static String capitalize(String str) { 1822 return Character.toUpperCase(str.charAt(0)) + str.substring(1); 1823 } 1824 1825 @SuppressLint("DefaultLocale") checkAndConvertGenre(ContentValues values)1826 private void checkAndConvertGenre(ContentValues values) { 1827 String canonicalGenres = values.getAsString(Programs.COLUMN_CANONICAL_GENRE); 1828 1829 if (!TextUtils.isEmpty(canonicalGenres)) { 1830 // Check if the canonical genres are valid. If not, clear them. 1831 String[] genres = Genres.decode(canonicalGenres); 1832 for (String genre : genres) { 1833 if (!Genres.isCanonical(genre)) { 1834 values.putNull(Programs.COLUMN_CANONICAL_GENRE); 1835 canonicalGenres = null; 1836 break; 1837 } 1838 } 1839 } 1840 1841 if (TextUtils.isEmpty(canonicalGenres)) { 1842 // If the canonical genre is not set, try to map the broadcast genre to the canonical 1843 // genre. 1844 String broadcastGenres = values.getAsString(Programs.COLUMN_BROADCAST_GENRE); 1845 if (!TextUtils.isEmpty(broadcastGenres)) { 1846 Set<String> genreSet = new HashSet<>(); 1847 String[] genres = Genres.decode(broadcastGenres); 1848 for (String genre : genres) { 1849 String canonicalGenre = sGenreMap.get(genre.toUpperCase()); 1850 if (Genres.isCanonical(canonicalGenre)) { 1851 genreSet.add(canonicalGenre); 1852 } 1853 } 1854 if (genreSet.size() > 0) { 1855 values.put(Programs.COLUMN_CANONICAL_GENRE, 1856 Genres.encode(genreSet.toArray(new String[genreSet.size()]))); 1857 } 1858 } 1859 } 1860 } 1861 checkAndConvertDeprecatedColumns(ContentValues values)1862 private void checkAndConvertDeprecatedColumns(ContentValues values) { 1863 if (values.containsKey(Programs.COLUMN_SEASON_NUMBER)) { 1864 if (!values.containsKey(Programs.COLUMN_SEASON_DISPLAY_NUMBER)) { 1865 values.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, values.getAsInteger( 1866 Programs.COLUMN_SEASON_NUMBER)); 1867 } 1868 values.remove(Programs.COLUMN_SEASON_NUMBER); 1869 } 1870 if (values.containsKey(Programs.COLUMN_EPISODE_NUMBER)) { 1871 if (!values.containsKey(Programs.COLUMN_EPISODE_DISPLAY_NUMBER)) { 1872 values.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, values.getAsInteger( 1873 Programs.COLUMN_EPISODE_NUMBER)); 1874 } 1875 values.remove(Programs.COLUMN_EPISODE_NUMBER); 1876 } 1877 } 1878 1879 // We might have more than one thread trying to make its way through applyBatch() so the 1880 // notification coalescing needs to be thread-local to work correctly. 1881 private final ThreadLocal<Set<Uri>> mTLBatchNotifications = new ThreadLocal<>(); 1882 getBatchNotificationsSet()1883 private Set<Uri> getBatchNotificationsSet() { 1884 return mTLBatchNotifications.get(); 1885 } 1886 setBatchNotificationsSet(Set<Uri> batchNotifications)1887 private void setBatchNotificationsSet(Set<Uri> batchNotifications) { 1888 mTLBatchNotifications.set(batchNotifications); 1889 } 1890 1891 @Override applyBatch(ArrayList<ContentProviderOperation> operations)1892 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 1893 throws OperationApplicationException { 1894 setBatchNotificationsSet(new HashSet<Uri>()); 1895 Context context = getContext(); 1896 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1897 db.beginTransaction(); 1898 try { 1899 ContentProviderResult[] results = super.applyBatch(operations); 1900 db.setTransactionSuccessful(); 1901 return results; 1902 } finally { 1903 db.endTransaction(); 1904 final Set<Uri> notifications = getBatchNotificationsSet(); 1905 setBatchNotificationsSet(null); 1906 for (final Uri uri : notifications) { 1907 context.getContentResolver().notifyChange(uri, null); 1908 } 1909 } 1910 } 1911 1912 @Override bulkInsert(Uri uri, ContentValues[] values)1913 public int bulkInsert(Uri uri, ContentValues[] values) { 1914 setBatchNotificationsSet(new HashSet<Uri>()); 1915 Context context = getContext(); 1916 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 1917 db.beginTransaction(); 1918 try { 1919 int result = super.bulkInsert(uri, values); 1920 db.setTransactionSuccessful(); 1921 return result; 1922 } finally { 1923 db.endTransaction(); 1924 final Set<Uri> notifications = getBatchNotificationsSet(); 1925 setBatchNotificationsSet(null); 1926 for (final Uri notificationUri : notifications) { 1927 context.getContentResolver().notifyChange(notificationUri, null); 1928 } 1929 } 1930 } 1931 notifyChange(Uri uri)1932 private void notifyChange(Uri uri) { 1933 final Set<Uri> batchNotifications = getBatchNotificationsSet(); 1934 if (batchNotifications != null) { 1935 batchNotifications.add(uri); 1936 } else { 1937 getContext().getContentResolver().notifyChange(uri, null); 1938 } 1939 } 1940 callerHasReadTvListingsPermission()1941 private boolean callerHasReadTvListingsPermission() { 1942 return getContext().checkCallingOrSelfPermission(PERMISSION_READ_TV_LISTINGS) 1943 == PackageManager.PERMISSION_GRANTED; 1944 } 1945 callerHasAccessAllEpgDataPermission()1946 private boolean callerHasAccessAllEpgDataPermission() { 1947 return getContext().checkCallingOrSelfPermission(PERMISSION_ACCESS_ALL_EPG_DATA) 1948 == PackageManager.PERMISSION_GRANTED; 1949 } 1950 callerHasAccessWatchedProgramsPermission()1951 private boolean callerHasAccessWatchedProgramsPermission() { 1952 return getContext().checkCallingOrSelfPermission(PERMISSION_ACCESS_WATCHED_PROGRAMS) 1953 == PackageManager.PERMISSION_GRANTED; 1954 } 1955 callerHasModifyParentalControlsPermission()1956 private boolean callerHasModifyParentalControlsPermission() { 1957 return getContext().checkCallingOrSelfPermission( 1958 android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) 1959 == PackageManager.PERMISSION_GRANTED; 1960 } 1961 blockIllegalAccessToIdAndPackageName(Uri uri, ContentValues values)1962 private void blockIllegalAccessToIdAndPackageName(Uri uri, ContentValues values) { 1963 if (values.containsKey(BaseColumns._ID)) { 1964 int match = sUriMatcher.match(uri); 1965 switch (match) { 1966 case MATCH_CHANNEL_ID: 1967 case MATCH_PROGRAM_ID: 1968 case MATCH_PREVIEW_PROGRAM_ID: 1969 case MATCH_RECORDED_PROGRAM_ID: 1970 case MATCH_WATCH_NEXT_PROGRAM_ID: 1971 case MATCH_WATCHED_PROGRAM_ID: 1972 if (TextUtils.equals(values.getAsString(BaseColumns._ID), 1973 uri.getLastPathSegment())) { 1974 break; 1975 } 1976 default: 1977 throw new IllegalArgumentException("Not allowed to change ID."); 1978 } 1979 } 1980 if (values.containsKey(BaseTvColumns.COLUMN_PACKAGE_NAME) 1981 && !callerHasAccessAllEpgDataPermission() && !TextUtils.equals(values.getAsString( 1982 BaseTvColumns.COLUMN_PACKAGE_NAME), getCallingPackage_())) { 1983 throw new SecurityException("Not allowed to change package name."); 1984 } 1985 } 1986 blockIllegalAccessToChannelsSystemColumns(ContentValues values)1987 private void blockIllegalAccessToChannelsSystemColumns(ContentValues values) { 1988 if (values.containsKey(Channels.COLUMN_LOCKED) 1989 && !callerHasModifyParentalControlsPermission()) { 1990 throw new SecurityException("Not allowed to access Channels.COLUMN_LOCKED"); 1991 } 1992 Boolean hasAccessAllEpgDataPermission = null; 1993 if (values.containsKey(Channels.COLUMN_BROWSABLE)) { 1994 hasAccessAllEpgDataPermission = callerHasAccessAllEpgDataPermission(); 1995 if (!hasAccessAllEpgDataPermission) { 1996 throw new SecurityException("Not allowed to access Channels.COLUMN_BROWSABLE"); 1997 } 1998 } 1999 } 2000 blockIllegalAccessToPreviewProgramsSystemColumns(ContentValues values)2001 private void blockIllegalAccessToPreviewProgramsSystemColumns(ContentValues values) { 2002 if (values.containsKey(PreviewPrograms.COLUMN_BROWSABLE) 2003 && !callerHasAccessAllEpgDataPermission()) { 2004 throw new SecurityException("Not allowed to access Programs.COLUMN_BROWSABLE"); 2005 } 2006 } 2007 blockIllegalAccessFromBlockedPackage()2008 private void blockIllegalAccessFromBlockedPackage() { 2009 String callingPackageName = getCallingPackage_(); 2010 if (sBlockedPackages.containsKey(callingPackageName)) { 2011 throw new SecurityException( 2012 "Not allowed to access " + TvContract.AUTHORITY + ", " 2013 + callingPackageName + " is blocked"); 2014 } 2015 } 2016 disallowModifyChannelType(ContentValues values, SqlParams params)2017 private boolean disallowModifyChannelType(ContentValues values, SqlParams params) { 2018 if (values.containsKey(Channels.COLUMN_TYPE)) { 2019 params.appendWhere(Channels.COLUMN_TYPE + "=?", 2020 values.getAsString(Channels.COLUMN_TYPE)); 2021 return true; 2022 } 2023 return false; 2024 } 2025 disallowModifyChannelId(ContentValues values, SqlParams params)2026 private boolean disallowModifyChannelId(ContentValues values, SqlParams params) { 2027 if (values.containsKey(PreviewPrograms.COLUMN_CHANNEL_ID)) { 2028 params.appendWhere(PreviewPrograms.COLUMN_CHANNEL_ID + "=?", 2029 values.getAsString(PreviewPrograms.COLUMN_CHANNEL_ID)); 2030 return true; 2031 } 2032 return false; 2033 } 2034 2035 @Override openFile(Uri uri, String mode)2036 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 2037 switch (sUriMatcher.match(uri)) { 2038 case MATCH_CHANNEL_ID_LOGO: 2039 return openLogoFile(uri, mode); 2040 default: 2041 throw new FileNotFoundException(uri.toString()); 2042 } 2043 } 2044 openLogoFile(Uri uri, String mode)2045 private ParcelFileDescriptor openLogoFile(Uri uri, String mode) throws FileNotFoundException { 2046 long channelId = Long.parseLong(uri.getPathSegments().get(1)); 2047 2048 SqlParams params = new SqlParams(CHANNELS_TABLE, Channels._ID + "=?", 2049 String.valueOf(channelId)); 2050 if (!callerHasAccessAllEpgDataPermission()) { 2051 if (callerHasReadTvListingsPermission()) { 2052 params.appendWhere(Channels.COLUMN_PACKAGE_NAME + "=? OR " 2053 + Channels.COLUMN_SEARCHABLE + "=?", getCallingPackage_(), "1"); 2054 } else { 2055 params.appendWhere(Channels.COLUMN_PACKAGE_NAME + "=?", getCallingPackage_()); 2056 } 2057 } 2058 2059 SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 2060 queryBuilder.setTables(params.getTables()); 2061 2062 // We don't write the database here. 2063 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 2064 if (mode.equals("r")) { 2065 String sql = queryBuilder.buildQuery(new String[] { CHANNELS_COLUMN_LOGO }, 2066 params.getSelection(), null, null, null, null); 2067 ParcelFileDescriptor fd = DatabaseUtils.blobFileDescriptorForQuery( 2068 db, sql, params.getSelectionArgs()); 2069 if (fd == null) { 2070 throw new FileNotFoundException(uri.toString()); 2071 } 2072 return fd; 2073 } else { 2074 try (Cursor cursor = queryBuilder.query(db, new String[] { Channels._ID }, 2075 params.getSelection(), params.getSelectionArgs(), null, null, null)) { 2076 if (cursor.getCount() < 1) { 2077 // Fails early if corresponding channel does not exist. 2078 // PipeMonitor may still fail to update DB later. 2079 throw new FileNotFoundException(uri.toString()); 2080 } 2081 } 2082 2083 try { 2084 ParcelFileDescriptor[] pipeFds = ParcelFileDescriptor.createPipe(); 2085 PipeMonitor pipeMonitor = new PipeMonitor(pipeFds[0], channelId, params); 2086 pipeMonitor.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 2087 return pipeFds[1]; 2088 } catch (IOException ioe) { 2089 FileNotFoundException fne = new FileNotFoundException(uri.toString()); 2090 fne.initCause(ioe); 2091 throw fne; 2092 } 2093 } 2094 } 2095 2096 /** 2097 * Validates the sort order based on the given field set. 2098 * 2099 * @throws IllegalArgumentException if there is any unknown field. 2100 */ 2101 @SuppressLint("DefaultLocale") validateSortOrder(String sortOrder, Set<String> possibleFields)2102 private static void validateSortOrder(String sortOrder, Set<String> possibleFields) { 2103 if (TextUtils.isEmpty(sortOrder) || possibleFields.isEmpty()) { 2104 return; 2105 } 2106 String[] orders = sortOrder.split(","); 2107 for (String order : orders) { 2108 String field = order.replaceAll("\\s+", " ").trim().toLowerCase().replace(" asc", "") 2109 .replace(" desc", ""); 2110 if (!possibleFields.contains(field)) { 2111 throw new IllegalArgumentException("Illegal field in sort order " + order); 2112 } 2113 } 2114 } 2115 2116 private class PipeMonitor extends AsyncTask<Void, Void, Void> { 2117 private final ParcelFileDescriptor mPfd; 2118 private final long mChannelId; 2119 private final SqlParams mParams; 2120 PipeMonitor(ParcelFileDescriptor pfd, long channelId, SqlParams params)2121 private PipeMonitor(ParcelFileDescriptor pfd, long channelId, SqlParams params) { 2122 mPfd = pfd; 2123 mChannelId = channelId; 2124 mParams = params; 2125 } 2126 2127 @Override doInBackground(Void... params)2128 protected Void doInBackground(Void... params) { 2129 AutoCloseInputStream is = new AutoCloseInputStream(mPfd); 2130 ByteArrayOutputStream baos = null; 2131 int count = 0; 2132 try { 2133 Bitmap bitmap = BitmapFactory.decodeStream(is); 2134 if (bitmap == null) { 2135 Log.e(TAG, "Failed to decode logo image for channel ID " + mChannelId); 2136 return null; 2137 } 2138 2139 float scaleFactor = Math.min(1f, ((float) MAX_LOGO_IMAGE_SIZE) / 2140 Math.max(bitmap.getWidth(), bitmap.getHeight())); 2141 if (scaleFactor < 1f) { 2142 bitmap = Bitmap.createScaledBitmap(bitmap, 2143 (int) (bitmap.getWidth() * scaleFactor), 2144 (int) (bitmap.getHeight() * scaleFactor), false); 2145 } 2146 2147 baos = new ByteArrayOutputStream(); 2148 bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); 2149 byte[] bytes = baos.toByteArray(); 2150 2151 ContentValues values = new ContentValues(); 2152 values.put(CHANNELS_COLUMN_LOGO, bytes); 2153 2154 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 2155 count = db.update(mParams.getTables(), values, mParams.getSelection(), 2156 mParams.getSelectionArgs()); 2157 if (count > 0) { 2158 Uri uri = TvContract.buildChannelLogoUri(mChannelId); 2159 notifyChange(uri); 2160 } 2161 } finally { 2162 if (count == 0) { 2163 try { 2164 mPfd.closeWithError("Failed to write logo for channel ID " + mChannelId); 2165 } catch (IOException ioe) { 2166 Log.e(TAG, "Failed to close pipe", ioe); 2167 } 2168 } 2169 IoUtils.closeQuietly(baos); 2170 IoUtils.closeQuietly(is); 2171 } 2172 return null; 2173 } 2174 } 2175 deleteUnconsolidatedWatchedProgramsRows()2176 private void deleteUnconsolidatedWatchedProgramsRows() { 2177 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 2178 db.delete(WATCHED_PROGRAMS_TABLE, WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=0", null); 2179 } 2180 2181 @SuppressLint("HandlerLeak") 2182 private final class WatchLogHandler extends Handler { 2183 private static final int MSG_CONSOLIDATE = 1; 2184 private static final int MSG_TRY_CONSOLIDATE_ALL = 2; 2185 2186 @Override handleMessage(Message msg)2187 public void handleMessage(Message msg) { 2188 switch (msg.what) { 2189 case MSG_CONSOLIDATE: { 2190 SomeArgs args = (SomeArgs) msg.obj; 2191 String sessionToken = (String) args.arg1; 2192 long watchEndTime = (long) args.arg2; 2193 onConsolidate(sessionToken, watchEndTime); 2194 args.recycle(); 2195 return; 2196 } 2197 case MSG_TRY_CONSOLIDATE_ALL: { 2198 onTryConsolidateAll(); 2199 return; 2200 } 2201 default: { 2202 Log.w(TAG, "Unhandled message code: " + msg.what); 2203 return; 2204 } 2205 } 2206 } 2207 2208 // Consolidates all WatchedPrograms rows for a given session with watch end time information 2209 // of the most recent log entry. After this method is called, it is guaranteed that there 2210 // remain consolidated rows only for that session. onConsolidate(String sessionToken, long watchEndTime)2211 private void onConsolidate(String sessionToken, long watchEndTime) { 2212 if (DEBUG) { 2213 Log.d(TAG, "onConsolidate(sessionToken=" + sessionToken + ", watchEndTime=" 2214 + watchEndTime + ")"); 2215 } 2216 2217 SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 2218 queryBuilder.setTables(WATCHED_PROGRAMS_TABLE); 2219 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 2220 2221 // Pick up the last row with the same session token. 2222 String[] projection = { 2223 WatchedPrograms._ID, 2224 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, 2225 WatchedPrograms.COLUMN_CHANNEL_ID 2226 }; 2227 String selection = WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=? AND " 2228 + WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN + "=?"; 2229 String[] selectionArgs = { 2230 "0", 2231 sessionToken 2232 }; 2233 String sortOrder = WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + " DESC"; 2234 2235 int consolidatedRowCount = 0; 2236 try (Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, 2237 null, sortOrder)) { 2238 long oldWatchStartTime = watchEndTime; 2239 while (cursor != null && cursor.moveToNext()) { 2240 long id = cursor.getLong(0); 2241 long watchStartTime = cursor.getLong(1); 2242 long channelId = cursor.getLong(2); 2243 consolidatedRowCount += consolidateRow(id, watchStartTime, oldWatchStartTime, 2244 channelId, false); 2245 oldWatchStartTime = watchStartTime; 2246 } 2247 } 2248 if (consolidatedRowCount > 0) { 2249 deleteUnsearchable(); 2250 } 2251 } 2252 2253 // Tries to consolidate all WatchedPrograms rows regardless of the session. After this 2254 // method is called, it is guaranteed that we have at most one unconsolidated log entry per 2255 // session that represents the user's ongoing watch activity. 2256 // Also, this method automatically schedules the next consolidation if there still remains 2257 // an unconsolidated entry. onTryConsolidateAll()2258 private void onTryConsolidateAll() { 2259 if (DEBUG) { 2260 Log.d(TAG, "onTryConsolidateAll()"); 2261 } 2262 2263 SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 2264 queryBuilder.setTables(WATCHED_PROGRAMS_TABLE); 2265 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 2266 2267 // Pick up all unconsolidated rows grouped by session. The most recent log entry goes on 2268 // top. 2269 String[] projection = { 2270 WatchedPrograms._ID, 2271 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, 2272 WatchedPrograms.COLUMN_CHANNEL_ID, 2273 WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN 2274 }; 2275 String selection = WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=0"; 2276 String sortOrder = WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN + " DESC," 2277 + WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + " DESC"; 2278 2279 int consolidatedRowCount = 0; 2280 try (Cursor cursor = queryBuilder.query(db, projection, selection, null, null, null, 2281 sortOrder)) { 2282 long oldWatchStartTime = 0; 2283 String oldSessionToken = null; 2284 while (cursor != null && cursor.moveToNext()) { 2285 long id = cursor.getLong(0); 2286 long watchStartTime = cursor.getLong(1); 2287 long channelId = cursor.getLong(2); 2288 String sessionToken = cursor.getString(3); 2289 2290 if (!sessionToken.equals(oldSessionToken)) { 2291 // The most recent log entry for the current session, which may be still 2292 // active. Just go through a dry run with the current time to see if this 2293 // entry can be split into multiple rows. 2294 consolidatedRowCount += consolidateRow(id, watchStartTime, 2295 System.currentTimeMillis(), channelId, true); 2296 oldSessionToken = sessionToken; 2297 } else { 2298 // The later entries after the most recent one all fall into here. We now 2299 // know that this watch activity ended exactly at the same time when the 2300 // next activity started. 2301 consolidatedRowCount += consolidateRow(id, watchStartTime, 2302 oldWatchStartTime, channelId, false); 2303 } 2304 oldWatchStartTime = watchStartTime; 2305 } 2306 } 2307 if (consolidatedRowCount > 0) { 2308 deleteUnsearchable(); 2309 } 2310 scheduleConsolidationIfNeeded(); 2311 } 2312 2313 // Consolidates a WatchedPrograms row. 2314 // A row is 'consolidated' if and only if the following information is complete: 2315 // 1. WatchedPrograms.COLUMN_CHANNEL_ID 2316 // 2. WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS 2317 // 3. WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS 2318 // where COLUMN_WATCH_START_TIME_UTC_MILLIS <= COLUMN_WATCH_END_TIME_UTC_MILLIS. 2319 // This is the minimal but useful enough set of information to comprise the user's watch 2320 // history. (The program data are considered optional although we do try to fill them while 2321 // consolidating the row.) It is guaranteed that the target row is either consolidated or 2322 // deleted after this method is called. 2323 // Set {@code dryRun} to {@code true} if you think it's necessary to split the row without 2324 // consolidating the most recent row because the user stayed on the same channel for a very 2325 // long time. 2326 // This method returns the number of consolidated rows, which can be 0 or more. consolidateRow( long id, long watchStartTime, long watchEndTime, long channelId, boolean dryRun)2327 private int consolidateRow( 2328 long id, long watchStartTime, long watchEndTime, long channelId, boolean dryRun) { 2329 if (DEBUG) { 2330 Log.d(TAG, "consolidateRow(id=" + id + ", watchStartTime=" + watchStartTime 2331 + ", watchEndTime=" + watchEndTime + ", channelId=" + channelId 2332 + ", dryRun=" + dryRun + ")"); 2333 } 2334 2335 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 2336 2337 if (watchStartTime > watchEndTime) { 2338 Log.e(TAG, "watchEndTime cannot be less than watchStartTime"); 2339 db.delete(WATCHED_PROGRAMS_TABLE, WatchedPrograms._ID + "=" + String.valueOf(id), 2340 null); 2341 return 0; 2342 } 2343 2344 ContentValues values = getProgramValues(channelId, watchStartTime); 2345 Long endTime = values.getAsLong(WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS); 2346 boolean needsToSplit = endTime != null && endTime < watchEndTime; 2347 2348 values.put(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, 2349 String.valueOf(watchStartTime)); 2350 if (!dryRun || needsToSplit) { 2351 values.put(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 2352 String.valueOf(needsToSplit ? endTime : watchEndTime)); 2353 values.put(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED, "1"); 2354 db.update(WATCHED_PROGRAMS_TABLE, values, 2355 WatchedPrograms._ID + "=" + String.valueOf(id), null); 2356 // Treat the watched program is inserted when WATCHED_PROGRAMS_COLUMN_CONSOLIDATED 2357 // becomes 1. 2358 notifyChange(TvContract.buildWatchedProgramUri(id)); 2359 } else { 2360 db.update(WATCHED_PROGRAMS_TABLE, values, 2361 WatchedPrograms._ID + "=" + String.valueOf(id), null); 2362 } 2363 int count = dryRun ? 0 : 1; 2364 if (needsToSplit) { 2365 // This means that the program ended before the user stops watching the current 2366 // channel. In this case we duplicate the log entry as many as the number of 2367 // programs watched on the same channel. Here the end time of the current program 2368 // becomes the new watch start time of the next program. 2369 long duplicatedId = duplicateRow(id); 2370 if (duplicatedId > 0) { 2371 count += consolidateRow(duplicatedId, endTime, watchEndTime, channelId, dryRun); 2372 } 2373 } 2374 return count; 2375 } 2376 2377 // Deletes the log entries from unsearchable channels. Note that only consolidated log 2378 // entries are safe to delete. deleteUnsearchable()2379 private void deleteUnsearchable() { 2380 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 2381 String deleteWhere = WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=1 AND " 2382 + WatchedPrograms.COLUMN_CHANNEL_ID + " IN (SELECT " + Channels._ID 2383 + " FROM " + CHANNELS_TABLE + " WHERE " + Channels.COLUMN_SEARCHABLE + "=0)"; 2384 db.delete(WATCHED_PROGRAMS_TABLE, deleteWhere, null); 2385 } 2386 scheduleConsolidationIfNeeded()2387 private void scheduleConsolidationIfNeeded() { 2388 if (DEBUG) { 2389 Log.d(TAG, "scheduleConsolidationIfNeeded()"); 2390 } 2391 SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 2392 queryBuilder.setTables(WATCHED_PROGRAMS_TABLE); 2393 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 2394 2395 // Pick up all unconsolidated rows. 2396 String[] projection = { 2397 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, 2398 WatchedPrograms.COLUMN_CHANNEL_ID, 2399 }; 2400 String selection = WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=0"; 2401 2402 try (Cursor cursor = queryBuilder.query(db, projection, selection, null, null, null, 2403 null)) { 2404 // Find the earliest time that any of the currently watching programs ends and 2405 // schedule the next consolidation at that time. 2406 long minEndTime = Long.MAX_VALUE; 2407 while (cursor != null && cursor.moveToNext()) { 2408 long watchStartTime = cursor.getLong(0); 2409 long channelId = cursor.getLong(1); 2410 ContentValues values = getProgramValues(channelId, watchStartTime); 2411 Long endTime = values.getAsLong(WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS); 2412 2413 if (endTime != null && endTime < minEndTime 2414 && endTime > System.currentTimeMillis()) { 2415 minEndTime = endTime; 2416 } 2417 } 2418 if (minEndTime != Long.MAX_VALUE) { 2419 sendEmptyMessageAtTime(MSG_TRY_CONSOLIDATE_ALL, minEndTime); 2420 if (DEBUG) { 2421 CharSequence minEndTimeStr = DateUtils.getRelativeTimeSpanString( 2422 minEndTime, System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS); 2423 Log.d(TAG, "onTryConsolidateAll() scheduled " + minEndTimeStr); 2424 } 2425 } 2426 } 2427 } 2428 2429 // Returns non-null ContentValues of the program data that the user watched on the channel 2430 // {@code channelId} at the time {@code time}. getProgramValues(long channelId, long time)2431 private ContentValues getProgramValues(long channelId, long time) { 2432 SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 2433 queryBuilder.setTables(PROGRAMS_TABLE); 2434 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 2435 2436 String[] projection = { 2437 Programs.COLUMN_TITLE, 2438 Programs.COLUMN_START_TIME_UTC_MILLIS, 2439 Programs.COLUMN_END_TIME_UTC_MILLIS, 2440 Programs.COLUMN_SHORT_DESCRIPTION 2441 }; 2442 String selection = Programs.COLUMN_CHANNEL_ID + "=? AND " 2443 + Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND " 2444 + Programs.COLUMN_END_TIME_UTC_MILLIS + ">?"; 2445 String[] selectionArgs = { 2446 String.valueOf(channelId), 2447 String.valueOf(time), 2448 String.valueOf(time) 2449 }; 2450 String sortOrder = Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC"; 2451 2452 try (Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, 2453 null, sortOrder)) { 2454 ContentValues values = new ContentValues(); 2455 if (cursor != null && cursor.moveToNext()) { 2456 values.put(WatchedPrograms.COLUMN_TITLE, cursor.getString(0)); 2457 values.put(WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, cursor.getLong(1)); 2458 values.put(WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, cursor.getLong(2)); 2459 values.put(WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3)); 2460 } 2461 return values; 2462 } 2463 } 2464 2465 // Duplicates the WatchedPrograms row with a given ID and returns the ID of the duplicated 2466 // row. Returns -1 if failed. duplicateRow(long id)2467 private long duplicateRow(long id) { 2468 if (DEBUG) { 2469 Log.d(TAG, "duplicateRow(" + id + ")"); 2470 } 2471 2472 SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 2473 queryBuilder.setTables(WATCHED_PROGRAMS_TABLE); 2474 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 2475 2476 String[] projection = { 2477 WatchedPrograms.COLUMN_PACKAGE_NAME, 2478 WatchedPrograms.COLUMN_CHANNEL_ID, 2479 WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN 2480 }; 2481 String selection = WatchedPrograms._ID + "=" + String.valueOf(id); 2482 2483 try (Cursor cursor = queryBuilder.query(db, projection, selection, null, null, null, 2484 null)) { 2485 long rowId = -1; 2486 if (cursor != null && cursor.moveToNext()) { 2487 ContentValues values = new ContentValues(); 2488 values.put(WatchedPrograms.COLUMN_PACKAGE_NAME, cursor.getString(0)); 2489 values.put(WatchedPrograms.COLUMN_CHANNEL_ID, cursor.getLong(1)); 2490 values.put(WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN, cursor.getString(2)); 2491 rowId = db.insert(WATCHED_PROGRAMS_TABLE, null, values); 2492 } 2493 return rowId; 2494 } 2495 } 2496 } 2497 } 2498