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