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