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