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