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