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