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