1bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo/*
2bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo * Copyright (C) 2014 The Android Open Source Project
3bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo *
4bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo * Licensed under the Apache License, Version 2.0 (the "License");
5bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo * you may not use this file except in compliance with the License.
6bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo * You may obtain a copy of the License at
7bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo *
8bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo *      http://www.apache.org/licenses/LICENSE-2.0
9bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo *
10bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo * Unless required by applicable law or agreed to in writing, software
11bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo * distributed under the License is distributed on an "AS IS" BASIS,
12bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo * See the License for the specific language governing permissions and
14bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo * limitations under the License.
15bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo */
16bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
17bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seopackage com.android.providers.tv;
18bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
190fd53133eed104e0c228376e0f24194a7f6ff724Jae Seoimport android.annotation.SuppressLint;
20ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Leeimport android.app.AlarmManager;
21ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Leeimport android.app.PendingIntent;
22bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.content.ContentProvider;
23fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seoimport android.content.ContentProviderOperation;
24fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seoimport android.content.ContentProviderResult;
25bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.content.ContentValues;
26bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.content.Context;
27ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Leeimport android.content.Intent;
28fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seoimport android.content.OperationApplicationException;
29bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.content.UriMatcher;
30bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.content.pm.PackageManager;
31bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.database.Cursor;
32bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.database.DatabaseUtils;
33bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.database.SQLException;
34bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.database.sqlite.SQLiteDatabase;
35bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.database.sqlite.SQLiteOpenHelper;
36bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.database.sqlite.SQLiteQueryBuilder;
378a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Leeimport android.graphics.Bitmap;
388a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Leeimport android.graphics.BitmapFactory;
396456b82982d57b46ea9f7dc87ac65a768e16cb73Jae Seoimport android.media.tv.TvContract;
406456b82982d57b46ea9f7dc87ac65a768e16cb73Jae Seoimport android.media.tv.TvContract.BaseTvColumns;
416456b82982d57b46ea9f7dc87ac65a768e16cb73Jae Seoimport android.media.tv.TvContract.Channels;
426456b82982d57b46ea9f7dc87ac65a768e16cb73Jae Seoimport android.media.tv.TvContract.Programs;
43d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Leeimport android.media.tv.TvContract.Programs.Genres;
446456b82982d57b46ea9f7dc87ac65a768e16cb73Jae Seoimport android.media.tv.TvContract.WatchedPrograms;
45bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.net.Uri;
468a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Leeimport android.os.AsyncTask;
470fd53133eed104e0c228376e0f24194a7f6ff724Jae Seoimport android.os.Handler;
480fd53133eed104e0c228376e0f24194a7f6ff724Jae Seoimport android.os.Message;
498a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Leeimport android.os.ParcelFileDescriptor;
508a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Leeimport android.os.ParcelFileDescriptor.AutoCloseInputStream;
51bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.text.TextUtils;
520fd53133eed104e0c228376e0f24194a7f6ff724Jae Seoimport android.text.format.DateUtils;
53bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport android.util.Log;
54bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
55ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Leeimport com.android.internal.annotations.VisibleForTesting;
560fd53133eed104e0c228376e0f24194a7f6ff724Jae Seoimport com.android.internal.os.SomeArgs;
570fd53133eed104e0c228376e0f24194a7f6ff724Jae Seoimport com.android.providers.tv.util.SqlParams;
58fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seoimport com.google.android.collect.Sets;
59fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo
608a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Leeimport libcore.io.IoUtils;
618a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee
62711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Leeimport java.io.ByteArrayOutputStream;
638a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Leeimport java.io.File;
648a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Leeimport java.io.FileNotFoundException;
658a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Leeimport java.io.IOException;
66fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seoimport java.util.ArrayList;
67bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seoimport java.util.HashMap;
68443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Leeimport java.util.HashSet;
69443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Leeimport java.util.Map;
70fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seoimport java.util.Set;
71bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
72bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo/**
73bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo * TV content provider. The contract between this provider and applications is defined in
7415201d2d9dbd6db6db99da3517f5a5d7802c45ceJae Seo * {@link android.media.tv.TvContract}.
75bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo */
76bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seopublic class TvProvider extends ContentProvider {
774cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo    private static final boolean DEBUG = false;
78bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    private static final String TAG = "TvProvider";
79bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
80d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    // Operation names for createSqlParams().
81d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    private static final String OP_QUERY = "query";
82d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    private static final String OP_UPDATE = "update";
83d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    private static final String OP_DELETE = "delete";
84d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee
855462b213ed14735289692cf525a46aa27fc3ba26Dongwon Kang    private static final int DATABASE_VERSION = 21;
86d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee    private static final String DATABASE_NAME = "tv.db";
87d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee    private static final String CHANNELS_TABLE = "channels";
88d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee    private static final String PROGRAMS_TABLE = "programs";
89d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee    private static final String WATCHED_PROGRAMS_TABLE = "watched_programs";
90e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim    // This table stores deleted channels, so that when the same channel is added back,
91e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim    // TvProvider can restore the locked & browsable state.
92e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim    private static final String DELETED_CHANNELS_TABLE = "deleted_channels";
93d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee    private static final String DEFAULT_CHANNELS_SORT_ORDER = Channels.COLUMN_DISPLAY_NUMBER
94d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee            + " ASC";
95d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee    private static final String DEFAULT_PROGRAMS_SORT_ORDER = Programs.COLUMN_START_TIME_UTC_MILLIS
96d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee            + " ASC";
97d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee    private static final String DEFAULT_WATCHED_PROGRAMS_SORT_ORDER =
98d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee            WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + " DESC";
99d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee    private static final String CHANNELS_TABLE_INNER_JOIN_PROGRAMS_TABLE = CHANNELS_TABLE
100d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee            + " INNER JOIN " + PROGRAMS_TABLE
1010fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            + " ON (" + CHANNELS_TABLE + "." + Channels._ID + "="
102d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee            + PROGRAMS_TABLE + "." + Programs.COLUMN_CHANNEL_ID + ")";
103d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee
104bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    private static final UriMatcher sUriMatcher;
105bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    private static final int MATCH_CHANNEL = 1;
106bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    private static final int MATCH_CHANNEL_ID = 2;
1078a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee    private static final int MATCH_CHANNEL_ID_LOGO = 3;
108b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee    private static final int MATCH_PASSTHROUGH_ID = 4;
109b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee    private static final int MATCH_PROGRAM = 5;
110b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee    private static final int MATCH_PROGRAM_ID = 6;
111b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee    private static final int MATCH_WATCHED_PROGRAM = 7;
112b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee    private static final int MATCH_WATCHED_PROGRAM_ID = 8;
113bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
114711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee    private static final String CHANNELS_COLUMN_LOGO = "logo";
1158a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee    private static final int MAX_LOGO_IMAGE_SIZE = 256;
1168a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee
1170fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo    // The internal column in the watched programs table to indicate whether the current log entry
1180fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo    // is consolidated or not. Unconsolidated entries may have columns with missing data.
1190fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo    private static final String WATCHED_PROGRAMS_COLUMN_CONSOLIDATED = "consolidated";
1200fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
121f5ed20db5e1239f1a28d63ef7bed36beeaebc2e2Jae Seo    private static final long MAX_PROGRAM_DATA_DELAY_IN_MILLIS = 10 * 1000; // 10 seconds
1220fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
123d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    private static Map<String, String> sChannelProjectionMap;
124d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    private static Map<String, String> sProgramProjectionMap;
125d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    private static Map<String, String> sWatchedProgramProjectionMap;
126bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
127bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    static {
128bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
129bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        sUriMatcher.addURI(TvContract.AUTHORITY, "channel", MATCH_CHANNEL);
130fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo        sUriMatcher.addURI(TvContract.AUTHORITY, "channel/#", MATCH_CHANNEL_ID);
1318a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        sUriMatcher.addURI(TvContract.AUTHORITY, "channel/#/logo", MATCH_CHANNEL_ID_LOGO);
132b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee        sUriMatcher.addURI(TvContract.AUTHORITY, "passthrough/*", MATCH_PASSTHROUGH_ID);
133bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        sUriMatcher.addURI(TvContract.AUTHORITY, "program", MATCH_PROGRAM);
134fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo        sUriMatcher.addURI(TvContract.AUTHORITY, "program/#", MATCH_PROGRAM_ID);
135bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        sUriMatcher.addURI(TvContract.AUTHORITY, "watched_program", MATCH_WATCHED_PROGRAM);
136fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo        sUriMatcher.addURI(TvContract.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID);
137bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
138bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        sChannelProjectionMap = new HashMap<String, String>();
139d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee        sChannelProjectionMap.put(Channels._ID, CHANNELS_TABLE + "." + Channels._ID);
140d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee        sChannelProjectionMap.put(Channels.COLUMN_PACKAGE_NAME,
141d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_PACKAGE_NAME);
142023be771801a56970689b2cfe4892d04a66e99b9Chulwoo Lee        sChannelProjectionMap.put(Channels.COLUMN_INPUT_ID,
143023be771801a56970689b2cfe4892d04a66e99b9Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_INPUT_ID);
144d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee        sChannelProjectionMap.put(Channels.COLUMN_TYPE,
145d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_TYPE);
146f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee        sChannelProjectionMap.put(Channels.COLUMN_SERVICE_TYPE,
147f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_TYPE);
148f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee        sChannelProjectionMap.put(Channels.COLUMN_ORIGINAL_NETWORK_ID,
149f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                CHANNELS_TABLE + "." + Channels.COLUMN_ORIGINAL_NETWORK_ID);
1507cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sChannelProjectionMap.put(Channels.COLUMN_TRANSPORT_STREAM_ID,
151d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_TRANSPORT_STREAM_ID);
15206379c1487f9f5b3fc6da48c41ab056289ca2b81Wonsik Kim        sChannelProjectionMap.put(Channels.COLUMN_SERVICE_ID,
15306379c1487f9f5b3fc6da48c41ab056289ca2b81Wonsik Kim                CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_ID);
154d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee        sChannelProjectionMap.put(Channels.COLUMN_DISPLAY_NUMBER,
155d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NUMBER);
156d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee        sChannelProjectionMap.put(Channels.COLUMN_DISPLAY_NAME,
157d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NAME);
158f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee        sChannelProjectionMap.put(Channels.COLUMN_NETWORK_AFFILIATION,
159f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                CHANNELS_TABLE + "." + Channels.COLUMN_NETWORK_AFFILIATION);
160d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee        sChannelProjectionMap.put(Channels.COLUMN_DESCRIPTION,
161d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_DESCRIPTION);
162f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee        sChannelProjectionMap.put(Channels.COLUMN_VIDEO_FORMAT,
163f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                CHANNELS_TABLE + "." + Channels.COLUMN_VIDEO_FORMAT);
164d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee        sChannelProjectionMap.put(Channels.COLUMN_BROWSABLE,
165d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_BROWSABLE);
166d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee        sChannelProjectionMap.put(Channels.COLUMN_SEARCHABLE,
167d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_SEARCHABLE);
168f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee        sChannelProjectionMap.put(Channels.COLUMN_LOCKED,
169f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                CHANNELS_TABLE + "." + Channels.COLUMN_LOCKED);
17015201d2d9dbd6db6db99da3517f5a5d7802c45ceJae Seo        sChannelProjectionMap.put(Channels.COLUMN_INTERNAL_PROVIDER_DATA,
171d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_DATA);
172d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee        sChannelProjectionMap.put(Channels.COLUMN_VERSION_NUMBER,
173d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                CHANNELS_TABLE + "." + Channels.COLUMN_VERSION_NUMBER);
174bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
175bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        sProgramProjectionMap = new HashMap<String, String>();
176bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        sProgramProjectionMap.put(Programs._ID, Programs._ID);
1777cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sProgramProjectionMap.put(Programs.COLUMN_PACKAGE_NAME, Programs.COLUMN_PACKAGE_NAME);
1787cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sProgramProjectionMap.put(Programs.COLUMN_CHANNEL_ID, Programs.COLUMN_CHANNEL_ID);
1797cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sProgramProjectionMap.put(Programs.COLUMN_TITLE, Programs.COLUMN_TITLE);
1801604d0f9529ad3d0fcaee47f8e87c7abd7da3675Youngsang Cho        sProgramProjectionMap.put(Programs.COLUMN_SEASON_NUMBER, Programs.COLUMN_SEASON_NUMBER);
1811604d0f9529ad3d0fcaee47f8e87c7abd7da3675Youngsang Cho        sProgramProjectionMap.put(Programs.COLUMN_EPISODE_NUMBER, Programs.COLUMN_EPISODE_NUMBER);
1821604d0f9529ad3d0fcaee47f8e87c7abd7da3675Youngsang Cho        sProgramProjectionMap.put(Programs.COLUMN_EPISODE_TITLE, Programs.COLUMN_EPISODE_TITLE);
1837cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sProgramProjectionMap.put(Programs.COLUMN_START_TIME_UTC_MILLIS,
1847cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                Programs.COLUMN_START_TIME_UTC_MILLIS);
1857cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sProgramProjectionMap.put(Programs.COLUMN_END_TIME_UTC_MILLIS,
1867cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                Programs.COLUMN_END_TIME_UTC_MILLIS);
1870389724e1aabf49351ffaf5e139b74b072d190cbSungsoo Lim        sProgramProjectionMap.put(Programs.COLUMN_BROADCAST_GENRE, Programs.COLUMN_BROADCAST_GENRE);
1880389724e1aabf49351ffaf5e139b74b072d190cbSungsoo Lim        sProgramProjectionMap.put(Programs.COLUMN_CANONICAL_GENRE, Programs.COLUMN_CANONICAL_GENRE);
18915201d2d9dbd6db6db99da3517f5a5d7802c45ceJae Seo        sProgramProjectionMap.put(Programs.COLUMN_SHORT_DESCRIPTION,
19015201d2d9dbd6db6db99da3517f5a5d7802c45ceJae Seo                Programs.COLUMN_SHORT_DESCRIPTION);
1917cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sProgramProjectionMap.put(Programs.COLUMN_LONG_DESCRIPTION,
1927cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                Programs.COLUMN_LONG_DESCRIPTION);
193f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee        sProgramProjectionMap.put(Programs.COLUMN_VIDEO_WIDTH, Programs.COLUMN_VIDEO_WIDTH);
194f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee        sProgramProjectionMap.put(Programs.COLUMN_VIDEO_HEIGHT, Programs.COLUMN_VIDEO_HEIGHT);
195f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee        sProgramProjectionMap.put(Programs.COLUMN_AUDIO_LANGUAGE, Programs.COLUMN_AUDIO_LANGUAGE);
1960389724e1aabf49351ffaf5e139b74b072d190cbSungsoo Lim        sProgramProjectionMap.put(Programs.COLUMN_CONTENT_RATING, Programs.COLUMN_CONTENT_RATING);
1970389724e1aabf49351ffaf5e139b74b072d190cbSungsoo Lim        sProgramProjectionMap.put(Programs.COLUMN_POSTER_ART_URI, Programs.COLUMN_POSTER_ART_URI);
1980389724e1aabf49351ffaf5e139b74b072d190cbSungsoo Lim        sProgramProjectionMap.put(Programs.COLUMN_THUMBNAIL_URI, Programs.COLUMN_THUMBNAIL_URI);
19915201d2d9dbd6db6db99da3517f5a5d7802c45ceJae Seo        sProgramProjectionMap.put(Programs.COLUMN_INTERNAL_PROVIDER_DATA,
20015201d2d9dbd6db6db99da3517f5a5d7802c45ceJae Seo                Programs.COLUMN_INTERNAL_PROVIDER_DATA);
2017cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sProgramProjectionMap.put(Programs.COLUMN_VERSION_NUMBER, Programs.COLUMN_VERSION_NUMBER);
202bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
203bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        sWatchedProgramProjectionMap = new HashMap<String, String>();
204bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        sWatchedProgramProjectionMap.put(WatchedPrograms._ID, WatchedPrograms._ID);
2057cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2067cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS);
2077cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2087cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS);
2097cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_CHANNEL_ID,
2107cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                WatchedPrograms.COLUMN_CHANNEL_ID);
2117cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_TITLE,
2127cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                WatchedPrograms.COLUMN_TITLE);
2137cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
2147cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS);
2157cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
2167cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS);
2177cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo        sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_DESCRIPTION,
2187cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                WatchedPrograms.COLUMN_DESCRIPTION);
2190fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2200fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS);
2210fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        sWatchedProgramProjectionMap.put(WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2220fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN);
2230fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        sWatchedProgramProjectionMap.put(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED,
2240fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                WATCHED_PROGRAMS_COLUMN_CONSOLIDATED);
225bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
226bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
227443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee    // Mapping from broadcast genre to canonical genre.
228443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee    private static Map<String, String> sGenreMap;
229443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee
23013b32cf3dd4eed429f5dda7f1cad6dc50f2b5b84Jae Seo    private static final String PERMISSION_ACCESS_ALL_EPG_DATA =
23155d148657809a115754aa06de2e20147f8a98696Jae Seo            "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA";
232bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
2334cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo    private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
2344cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo            "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";
2354cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo
236bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    private static class DatabaseHelper extends SQLiteOpenHelper {
2370fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        private final Context mContext;
238711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee
239bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        DatabaseHelper(Context context) {
240bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            super(context, DATABASE_NAME, null, DATABASE_VERSION);
241711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee            mContext = context;
242bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
243bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
244bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        @Override
245fa6697c5c240228a4382896570cf197977ab99f7Jae Seo        public void onConfigure(SQLiteDatabase db) {
246fa6697c5c240228a4382896570cf197977ab99f7Jae Seo            db.setForeignKeyConstraintsEnabled(true);
247fa6697c5c240228a4382896570cf197977ab99f7Jae Seo        }
248fa6697c5c240228a4382896570cf197977ab99f7Jae Seo
249fa6697c5c240228a4382896570cf197977ab99f7Jae Seo        @Override
250bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        public void onCreate(SQLiteDatabase db) {
251bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            if (DEBUG) {
252bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                Log.d(TAG, "Creating database");
253bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            }
254bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            // Set up the database schema.
255bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            db.execSQL("CREATE TABLE " + CHANNELS_TABLE + " ("
256bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                    + Channels._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
2577cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Channels.COLUMN_PACKAGE_NAME + " TEXT NOT NULL,"
258023be771801a56970689b2cfe4892d04a66e99b9Chulwoo Lee                    + Channels.COLUMN_INPUT_ID + " TEXT NOT NULL,"
2595462b213ed14735289692cf525a46aa27fc3ba26Dongwon Kang                    + Channels.COLUMN_TYPE + " TEXT NOT NULL DEFAULT '" + Channels.TYPE_OTHER + "',"
2605462b213ed14735289692cf525a46aa27fc3ba26Dongwon Kang                    + Channels.COLUMN_SERVICE_TYPE + " TEXT NOT NULL DEFAULT '"
2615462b213ed14735289692cf525a46aa27fc3ba26Dongwon Kang                    + Channels.SERVICE_TYPE_AUDIO_VIDEO + "',"
262628460fc4c6ab290aea312dc8a912bde65676815Chulwoo Lee                    + Channels.COLUMN_ORIGINAL_NETWORK_ID + " INTEGER NOT NULL DEFAULT 0,"
263628460fc4c6ab290aea312dc8a912bde65676815Chulwoo Lee                    + Channels.COLUMN_TRANSPORT_STREAM_ID + " INTEGER NOT NULL DEFAULT 0,"
264628460fc4c6ab290aea312dc8a912bde65676815Chulwoo Lee                    + Channels.COLUMN_SERVICE_ID + " INTEGER NOT NULL DEFAULT 0,"
2657cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Channels.COLUMN_DISPLAY_NUMBER + " TEXT,"
2667cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Channels.COLUMN_DISPLAY_NAME + " TEXT,"
267f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                    + Channels.COLUMN_NETWORK_AFFILIATION + " TEXT,"
2687cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Channels.COLUMN_DESCRIPTION + " TEXT,"
269f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                    + Channels.COLUMN_VIDEO_FORMAT + " TEXT,"
2706c03ca644bcc3da46587390cce29eba3afdadddfJae Seo                    + Channels.COLUMN_BROWSABLE + " INTEGER NOT NULL DEFAULT 0,"
271992930b401cdf9dd136473fe514b70b3e213926aChulwoo Lee                    + Channels.COLUMN_SEARCHABLE + " INTEGER NOT NULL DEFAULT 1,"
272f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                    + Channels.COLUMN_LOCKED + " INTEGER NOT NULL DEFAULT 0,"
27315201d2d9dbd6db6db99da3517f5a5d7802c45ceJae Seo                    + Channels.COLUMN_INTERNAL_PROVIDER_DATA + " BLOB,"
274711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                    + CHANNELS_COLUMN_LOGO + " BLOB,"
275c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                    + Channels.COLUMN_VERSION_NUMBER + " INTEGER,"
276628460fc4c6ab290aea312dc8a912bde65676815Chulwoo Lee                    + "UNIQUE(" + Channels._ID + "," + Channels.COLUMN_PACKAGE_NAME + "),"
277628460fc4c6ab290aea312dc8a912bde65676815Chulwoo Lee                    + "UNIQUE(" + Channels.COLUMN_INPUT_ID + ","
278628460fc4c6ab290aea312dc8a912bde65676815Chulwoo Lee                            + Channels.COLUMN_ORIGINAL_NETWORK_ID + ","
279628460fc4c6ab290aea312dc8a912bde65676815Chulwoo Lee                            + Channels.COLUMN_TRANSPORT_STREAM_ID + ","
280628460fc4c6ab290aea312dc8a912bde65676815Chulwoo Lee                            + Channels.COLUMN_SERVICE_ID + ")"
281bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                    + ");");
282bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            db.execSQL("CREATE TABLE " + PROGRAMS_TABLE + " ("
283bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                    + Programs._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
2847cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Programs.COLUMN_PACKAGE_NAME + " TEXT NOT NULL,"
2857cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Programs.COLUMN_CHANNEL_ID + " INTEGER,"
2867cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Programs.COLUMN_TITLE + " TEXT,"
2871604d0f9529ad3d0fcaee47f8e87c7abd7da3675Youngsang Cho                    + Programs.COLUMN_SEASON_NUMBER + " INTEGER,"
2881604d0f9529ad3d0fcaee47f8e87c7abd7da3675Youngsang Cho                    + Programs.COLUMN_EPISODE_NUMBER + " INTEGER,"
2891604d0f9529ad3d0fcaee47f8e87c7abd7da3675Youngsang Cho                    + Programs.COLUMN_EPISODE_TITLE + " TEXT,"
2907cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Programs.COLUMN_START_TIME_UTC_MILLIS + " INTEGER,"
2917cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Programs.COLUMN_END_TIME_UTC_MILLIS + " INTEGER,"
292d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                    + Programs.COLUMN_BROADCAST_GENRE + " TEXT,"
293d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee                    + Programs.COLUMN_CANONICAL_GENRE + " TEXT,"
29415201d2d9dbd6db6db99da3517f5a5d7802c45ceJae Seo                    + Programs.COLUMN_SHORT_DESCRIPTION + " TEXT,"
2957cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Programs.COLUMN_LONG_DESCRIPTION + " TEXT,"
296f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                    + Programs.COLUMN_VIDEO_WIDTH + " INTEGER,"
297f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                    + Programs.COLUMN_VIDEO_HEIGHT + " INTEGER,"
298f0b8729f2dc0834129904b7bf10b8ac9af2bffaaJi-Hwan Lee                    + Programs.COLUMN_AUDIO_LANGUAGE + " TEXT,"
2990389724e1aabf49351ffaf5e139b74b072d190cbSungsoo Lim                    + Programs.COLUMN_CONTENT_RATING + " TEXT,"
3008a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                    + Programs.COLUMN_POSTER_ART_URI + " TEXT,"
3018a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                    + Programs.COLUMN_THUMBNAIL_URI + " TEXT,"
30215201d2d9dbd6db6db99da3517f5a5d7802c45ceJae Seo                    + Programs.COLUMN_INTERNAL_PROVIDER_DATA + " BLOB,"
3037cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + Programs.COLUMN_VERSION_NUMBER + " INTEGER,"
304c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                    + "FOREIGN KEY("
305c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                            + Programs.COLUMN_CHANNEL_ID + "," + Programs.COLUMN_PACKAGE_NAME
306c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                            + ") REFERENCES " + CHANNELS_TABLE + "("
307c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                            + Channels._ID + "," + Channels.COLUMN_PACKAGE_NAME
308c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                            + ") ON UPDATE CASCADE ON DELETE CASCADE"
309bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                    + ");");
310bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            db.execSQL("CREATE TABLE " + WATCHED_PROGRAMS_TABLE + " ("
311bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                    + WatchedPrograms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
31237f64851eaf6be870c8bc590bc863f1a4f9cc0fcJae Seo                    + WatchedPrograms.COLUMN_PACKAGE_NAME + " TEXT NOT NULL,"
3130fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS
3140fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + " INTEGER NOT NULL DEFAULT 0,"
3150fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS
3160fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + " INTEGER NOT NULL DEFAULT 0,"
3177cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + WatchedPrograms.COLUMN_CHANNEL_ID + " INTEGER,"
3187cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + WatchedPrograms.COLUMN_TITLE + " TEXT,"
3197cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS + " INTEGER,"
3207cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS + " INTEGER,"
3217cbf5dbfd20cd1a4ed35bc7c1f170c9e30a50f05Jae Seo                    + WatchedPrograms.COLUMN_DESCRIPTION + " TEXT,"
3220fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS + " TEXT,"
3230fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN + " TEXT NOT NULL,"
3240fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + " INTEGER NOT NULL DEFAULT 0,"
325c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                    + "FOREIGN KEY("
326c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                            + WatchedPrograms.COLUMN_CHANNEL_ID + ","
327c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                            + WatchedPrograms.COLUMN_PACKAGE_NAME
328c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                            + ") REFERENCES " + CHANNELS_TABLE + "("
329c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                            + Channels._ID + "," + Channels.COLUMN_PACKAGE_NAME
330c9d7274db686189d6ac9d09a07b3d0286fc81fa0Ji-Hwan Lee                            + ") ON UPDATE CASCADE ON DELETE CASCADE"
331bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                    + ");");
332e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim            db.execSQL("CREATE TABLE " + DELETED_CHANNELS_TABLE + " ("
333d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                    + Channels.COLUMN_INPUT_ID + " TEXT NOT NULL,"
334d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                    + Channels.COLUMN_ORIGINAL_NETWORK_ID + " INTEGER NOT NULL,"
335d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                    + Channels.COLUMN_TRANSPORT_STREAM_ID + " INTEGER NOT NULL,"
336d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                    + Channels.COLUMN_SERVICE_ID + " INTEGER NOT NULL,"
337e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                    + Channels.COLUMN_LOCKED + " INTEGER NOT NULL,"
338e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                    + Channels.COLUMN_BROWSABLE + " INTEGER NOT NULL,"
339d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                    + "UNIQUE(" + Channels.COLUMN_INPUT_ID + ","
340d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                    + Channels.COLUMN_ORIGINAL_NETWORK_ID + ","
341d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                    + Channels.COLUMN_TRANSPORT_STREAM_ID + "," + Channels.COLUMN_SERVICE_ID + ")"
342d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                    + ");");
343bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
344bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
345bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        @Override
346bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
347bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            if (DEBUG) {
348bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                Log.d(TAG, "Upgrading database from " + oldVersion + " to " + newVersion);
349bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            }
350bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
351bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            // Default upgrade case.
352e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim            db.execSQL("DROP TABLE IF EXISTS " + DELETED_CHANNELS_TABLE);
353bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            db.execSQL("DROP TABLE IF EXISTS " + WATCHED_PROGRAMS_TABLE);
3545f930ebc1e117d6c3ec53d22e296901857a45ae4Ji-Hwan Lee            db.execSQL("DROP TABLE IF EXISTS " + PROGRAMS_TABLE);
3555f930ebc1e117d6c3ec53d22e296901857a45ae4Ji-Hwan Lee            db.execSQL("DROP TABLE IF EXISTS " + CHANNELS_TABLE);
356711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee
357711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee            // Clear legacy logo directory
358711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee            File logoPath = new File(mContext.getFilesDir(), "logo");
359711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee            if (logoPath.exists()) {
360711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                for (File file : logoPath.listFiles()) {
361711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                    file.delete();
362711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                }
363711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                logoPath.delete();
364711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee            }
365711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee
366bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            onCreate(db);
367bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
368bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
369bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
370bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    private DatabaseHelper mOpenHelper;
371bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
3720fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo    private final Handler mLogHandler = new WatchLogHandler();
3730fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
374bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    @Override
375bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    public boolean onCreate() {
376bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        if (DEBUG) {
377d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim            Log.d(TAG, "Creating TvProvider");
378bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
379bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        mOpenHelper = new DatabaseHelper(getContext());
3800fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        deleteUnconsolidatedWatchedProgramsRows();
381ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee        scheduleEpgDataCleanup();
382443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        buildGenreMap();
383bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        return true;
384bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
385bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
386ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee    @VisibleForTesting
387ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee    void scheduleEpgDataCleanup() {
388ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee        Intent intent = new Intent(EpgDataCleanupService.ACTION_CLEAN_UP_EPG_DATA);
389ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee        intent.setClass(getContext(), EpgDataCleanupService.class);
390ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee        PendingIntent pendingIntent = PendingIntent.getService(
391ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee                getContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
392ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee        AlarmManager alarmManager =
393ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee                (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
394ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee        alarmManager.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis(),
395ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee                AlarmManager.INTERVAL_HALF_DAY, pendingIntent);
396ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee    }
397ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee
398443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee    private void buildGenreMap() {
399443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        if (sGenreMap != null) {
400443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            return;
401443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        }
402443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee
403443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        sGenreMap = new HashMap<String, String>();
404443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        buildGenreMap(R.array.genre_mapping_atsc);
405443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        buildGenreMap(R.array.genre_mapping_dvb);
4065d05aa72ee869d63828305e86a784b30b795f411Chulwoo Lee        buildGenreMap(R.array.genre_mapping_isdb);
407443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee    }
408443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee
4090fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo    @SuppressLint("DefaultLocale")
410443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee    private void buildGenreMap(int id) {
411443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        String[] maps = getContext().getResources().getStringArray(id);
412443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        for (String map : maps) {
413443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            String[] arr = map.split("\\|");
414443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            if (arr.length != 2) {
415443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                throw new IllegalArgumentException("Invalid genre mapping : " + map);
416443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            }
417443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            sGenreMap.put(arr[0].toUpperCase(), arr[1]);
418443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        }
419443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee    }
420443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee
421ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee    @VisibleForTesting
422ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee    String getCallingPackage_() {
423ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee        return getCallingPackage();
424ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee    }
425ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee
426bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    @Override
427bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    public String getType(Uri uri) {
428bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        switch (sUriMatcher.match(uri)) {
429bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_CHANNEL:
430bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                return Channels.CONTENT_TYPE;
431bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_CHANNEL_ID:
432bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                return Channels.CONTENT_ITEM_TYPE;
4338a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            case MATCH_CHANNEL_ID_LOGO:
4348a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                return "image/png";
435b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee            case MATCH_PASSTHROUGH_ID:
436cf688af6c5dcc9e6926b6b3e8fdf5732b9ec36a8Youngsang Cho                return Channels.CONTENT_ITEM_TYPE;
437bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_PROGRAM:
438bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                return Programs.CONTENT_TYPE;
439bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_PROGRAM_ID:
440bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                return Programs.CONTENT_ITEM_TYPE;
441bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_WATCHED_PROGRAM:
442bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                return WatchedPrograms.CONTENT_TYPE;
443bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_WATCHED_PROGRAM_ID:
444bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                return WatchedPrograms.CONTENT_ITEM_TYPE;
445bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            default:
446bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                throw new IllegalArgumentException("Unknown URI " + uri);
447bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
448bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
449bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
450bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    @Override
451bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
452bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            String sortOrder) {
453d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        if (needsToLimitPackage(uri) && !TextUtils.isEmpty(sortOrder)) {
4542f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo            throw new SecurityException("Sort order not allowed for " + uri);
455fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo        }
456d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        SqlParams params = createSqlParams(OP_QUERY, uri, selection, selectionArgs);
457fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo
458bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
459d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        queryBuilder.setTables(params.getTables());
460bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        String orderBy;
461d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        if (params.getTables().equals(PROGRAMS_TABLE)) {
462d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            queryBuilder.setProjectionMap(sProgramProjectionMap);
463d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            orderBy = DEFAULT_PROGRAMS_SORT_ORDER;
464d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        } else if (params.getTables().equals(WATCHED_PROGRAMS_TABLE)) {
465d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            queryBuilder.setProjectionMap(sWatchedProgramProjectionMap);
466d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            orderBy = DEFAULT_WATCHED_PROGRAMS_SORT_ORDER;
467d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        } else {
468d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            queryBuilder.setProjectionMap(sChannelProjectionMap);
469d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            orderBy = DEFAULT_CHANNELS_SORT_ORDER;
470bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
471bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
472bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        // Use the default sort order only if no sort order is specified.
473bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        if (!TextUtils.isEmpty(sortOrder)) {
474bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            orderBy = sortOrder;
475bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
476bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
477bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        // Get the database and run the query.
478bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
479d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        Cursor c = queryBuilder.query(db, projection, params.getSelection(),
480d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                params.getSelectionArgs(), null, null, orderBy);
481bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
482bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        // Tell the cursor what URI to watch, so it knows when its source data changes.
483bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        c.setNotificationUri(getContext().getContentResolver(), uri);
484bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        return c;
485bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
486bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
487bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    @Override
488bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    public Uri insert(Uri uri, ContentValues values) {
489bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        switch (sUriMatcher.match(uri)) {
490bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_CHANNEL:
491bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                return insertChannel(uri, values);
492bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_PROGRAM:
493bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                return insertProgram(uri, values);
494bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_WATCHED_PROGRAM:
495bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                return insertWatchedProgram(uri, values);
496916624c91fe0e53aa3e7b220b6f488ac6507285bJi-Hwan Lee            case MATCH_CHANNEL_ID:
497916624c91fe0e53aa3e7b220b6f488ac6507285bJi-Hwan Lee            case MATCH_CHANNEL_ID_LOGO:
498b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee            case MATCH_PASSTHROUGH_ID:
499916624c91fe0e53aa3e7b220b6f488ac6507285bJi-Hwan Lee            case MATCH_PROGRAM_ID:
500916624c91fe0e53aa3e7b220b6f488ac6507285bJi-Hwan Lee            case MATCH_WATCHED_PROGRAM_ID:
501916624c91fe0e53aa3e7b220b6f488ac6507285bJi-Hwan Lee                throw new UnsupportedOperationException("Cannot insert into that URI: " + uri);
502bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            default:
503bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                throw new IllegalArgumentException("Unknown URI " + uri);
504bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
505bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
506bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
507e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim    private static void restoreChannelState(SQLiteDatabase db, ContentValues values) {
508d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        String inputId = values.getAsString(Channels.COLUMN_INPUT_ID);
509d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        Integer onid = values.getAsInteger(Channels.COLUMN_ORIGINAL_NETWORK_ID);
510d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        Integer tsid = values.getAsInteger(Channels.COLUMN_TRANSPORT_STREAM_ID);
511d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        Integer serviceId = values.getAsInteger(Channels.COLUMN_SERVICE_ID);
512d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        if (onid == null) { onid = 0; }
513d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        if (tsid == null) { tsid = 0; }
514d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        if (serviceId == null) { serviceId = 0; }
515d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim
516e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim        SqlParams params = new SqlParams(DELETED_CHANNELS_TABLE,
517d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                "(" + Channels.COLUMN_INPUT_ID + "=?) AND ("
518d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                + Channels.COLUMN_ORIGINAL_NETWORK_ID + "=?) AND ("
519d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                + Channels.COLUMN_TRANSPORT_STREAM_ID + "=?) AND ("
520d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                + Channels.COLUMN_SERVICE_ID + "=?)",
521d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                inputId, Integer.toString(onid), Integer.toString(tsid),
522d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                Integer.toString(serviceId));
523d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim
524d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
525d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        queryBuilder.setTables(params.getTables());
526e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim        final String[] projection = { Channels.COLUMN_LOCKED, Channels.COLUMN_BROWSABLE };
527e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim        try (Cursor cursor = queryBuilder.query(db, projection, params.getSelection(),
528e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                params.getSelectionArgs(), null, null, null)) {
529e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim            if (cursor != null && cursor.moveToNext()) {
530e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                if (!values.containsKey(Channels.COLUMN_LOCKED)) {
531e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                    values.put(Channels.COLUMN_LOCKED, cursor.getInt(0));
532e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                }
533e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                if (!values.containsKey(Channels.COLUMN_BROWSABLE)) {
534e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                    values.put(Channels.COLUMN_BROWSABLE, cursor.getInt(1));
535e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                }
536e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                db.delete(params.getTables(), params.getSelection(), params.getSelectionArgs());
537d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim            }
538d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        }
539d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim    }
540d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim
541bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    private Uri insertChannel(Uri uri, ContentValues values) {
542bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        // Mark the owner package of this channel.
543ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee        values.put(Channels.COLUMN_PACKAGE_NAME, getCallingPackage_());
544bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
545bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
546e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim        restoreChannelState(db, values);
547bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        long rowId = db.insert(CHANNELS_TABLE, null, values);
548bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        if (rowId > 0) {
549bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            Uri channelUri = TvContract.buildChannelUri(rowId);
550fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            notifyChange(channelUri);
551bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            return channelUri;
552bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
553bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
554bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        throw new SQLException("Failed to insert row into " + uri);
555bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
556bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
557bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    private Uri insertProgram(Uri uri, ContentValues values) {
558bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        // Mark the owner package of this program.
559ac117ad70762672243573f9c8a5ea3220c4884bfJi-Hwan Lee        values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_());
560bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
561443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        checkAndConvertGenre(values);
562d308c6157aa068ee75425dae1e852b57fe5872b7Chulwoo Lee
563bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
564bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        long rowId = db.insert(PROGRAMS_TABLE, null, values);
565bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        if (rowId > 0) {
566bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            Uri programUri = TvContract.buildProgramUri(rowId);
567fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            notifyChange(programUri);
568bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            return programUri;
569bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
570bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
571bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        throw new SQLException("Failed to insert row into " + uri);
572bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
573bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
574bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    private Uri insertWatchedProgram(Uri uri, ContentValues values) {
5750fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        if (DEBUG) {
576c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo            Log.d(TAG, "insertWatchedProgram(uri=" + uri + ", values={" + values + "})");
577bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
5780fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        Long watchStartTime = values.getAsLong(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS);
5790fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        Long watchEndTime = values.getAsLong(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS);
5800fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // The system sends only two kinds of watch events:
5810fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // 1. The user tunes to a new channel. (COLUMN_WATCH_START_TIME_UTC_MILLIS)
5820fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // 2. The user stops watching. (COLUMN_WATCH_END_TIME_UTC_MILLIS)
5830fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        if (watchStartTime != null && watchEndTime == null) {
5840fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
5850fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            long rowId = db.insert(WATCHED_PROGRAMS_TABLE, null, values);
5860fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            if (rowId > 0) {
5870fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                mLogHandler.removeMessages(WatchLogHandler.MSG_TRY_CONSOLIDATE_ALL);
588f5ed20db5e1239f1a28d63ef7bed36beeaebc2e2Jae Seo                mLogHandler.sendEmptyMessageDelayed(WatchLogHandler.MSG_TRY_CONSOLIDATE_ALL,
589f5ed20db5e1239f1a28d63ef7bed36beeaebc2e2Jae Seo                        MAX_PROGRAM_DATA_DELAY_IN_MILLIS);
59066deb422aaf1bc5400bdcefac78d3e2c6c4f3189Sungsoo Lim                return TvContract.buildWatchedProgramUri(rowId);
5910fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
5920fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            throw new SQLException("Failed to insert row into " + uri);
5930fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        } else if (watchStartTime == null && watchEndTime != null) {
5940fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SomeArgs args = SomeArgs.obtain();
5950fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            args.arg1 = values.getAsString(WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN);
5960fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            args.arg2 = watchEndTime;
5970fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            Message msg = mLogHandler.obtainMessage(WatchLogHandler.MSG_CONSOLIDATE, args);
5980fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            mLogHandler.sendMessageDelayed(msg, MAX_PROGRAM_DATA_DELAY_IN_MILLIS);
5990fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            return null;
6000fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        }
6010fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // All the other cases are invalid.
6020fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        throw new IllegalArgumentException("Only one of COLUMN_WATCH_START_TIME_UTC_MILLIS and"
6030fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                + " COLUMN_WATCH_END_TIME_UTC_MILLIS should be specified");
604bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
605bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
606e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim    private static void storeChannelStates(SqlParams params, SQLiteDatabase db) {
607d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
608e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim        queryBuilder.setTables(params.getTables());
609d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        try (Cursor cursor = queryBuilder.query(db,
610d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                new String[] { Channels.COLUMN_INPUT_ID, Channels.COLUMN_ORIGINAL_NETWORK_ID,
611e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                Channels.COLUMN_TRANSPORT_STREAM_ID, Channels.COLUMN_SERVICE_ID,
612e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                Channels.COLUMN_LOCKED, Channels.COLUMN_BROWSABLE },
613e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                params.getSelection(), params.getSelectionArgs(), null, null, null)) {
614d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim            ContentValues values = new ContentValues();
615d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim            while (cursor != null && cursor.moveToNext()) {
616d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                values.put(Channels.COLUMN_INPUT_ID, cursor.getString(0));
617d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, cursor.getInt(1));
618d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, cursor.getInt(2));
619d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim                values.put(Channels.COLUMN_SERVICE_ID, cursor.getInt(3));
620e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                values.put(Channels.COLUMN_LOCKED, cursor.getInt(4));
621e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                values.put(Channels.COLUMN_BROWSABLE, cursor.getInt(5));
622e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim                db.insert(DELETED_CHANNELS_TABLE, null, values);
623d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim            }
624d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        }
625d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim    }
626d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim
627bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    @Override
628bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    public int delete(Uri uri, String selection, String[] selectionArgs) {
629d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        SqlParams params = createSqlParams(OP_DELETE, uri, selection, selectionArgs);
630bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
631d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        if (params.getTables().equals(CHANNELS_TABLE)) {
632e7957cfb42fcfdc5710246d80b980e232aac1ef7Wonsik Kim            storeChannelStates(params, db);
633d79322ce0f814dc7acfebbb2481f765259a736ceWonsik Kim        }
634d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        int count = db.delete(params.getTables(), params.getSelection(), params.getSelectionArgs());
635bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        if (count > 0) {
636fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            notifyChange(uri);
637bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
638bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        return count;
639bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
640bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
641bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    @Override
642bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
643d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        SqlParams params = createSqlParams(OP_UPDATE, uri, selection, selectionArgs);
6442f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo        if (params.getTables().equals(CHANNELS_TABLE)) {
6452f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo            if (values.containsKey(Channels.COLUMN_LOCKED)
6462f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo                    && !callerHasModifyParentalControlsPermission()) {
6472f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo                throw new SecurityException("Not allowed to modify Channels.COLUMN_LOCKED");
6482f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo            }
6492f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo        } else if (params.getTables().equals(PROGRAMS_TABLE)) {
650d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            checkAndConvertGenre(values);
651d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        }
652d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
653d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        int count = db.update(params.getTables(), values, params.getSelection(),
654d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                params.getSelectionArgs());
655d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        if (count > 0) {
656d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            notifyChange(uri);
657d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        }
658d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        return count;
659d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    }
660d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee
661d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    private SqlParams createSqlParams(String operation, Uri uri, String selection,
662d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            String[] selectionArgs) {
663d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        SqlParams params = new SqlParams(null, selection, selectionArgs);
664bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        if (needsToLimitPackage(uri)) {
665fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo            if (!TextUtils.isEmpty(selection)) {
6662f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo                throw new SecurityException("Selection not allowed for " + uri);
667fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo            }
668d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            params.setWhere(BaseTvColumns.COLUMN_PACKAGE_NAME + "=?", getCallingPackage_());
669bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
670bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        switch (sUriMatcher.match(uri)) {
671bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_CHANNEL:
672b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                String genre = uri.getQueryParameter(TvContract.PARAM_CANONICAL_GENRE);
673b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                if (genre == null) {
674b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    params.setTables(CHANNELS_TABLE);
675b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                } else {
6761f29945e3b1f90ad287df664be30c4296dc96ff4Ji-Hwan Lee                    if (!operation.equals(OP_QUERY)) {
6772f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo                        throw new SecurityException(capitalize(operation)
678b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                                + " not allowed for " + uri);
679b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    }
680b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    if (!Genres.isCanonical(genre)) {
681b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                        throw new IllegalArgumentException("Not a canonical genre : " + genre);
682b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    }
683b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    params.setTables(CHANNELS_TABLE_INNER_JOIN_PROGRAMS_TABLE);
684b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    String curTime = String.valueOf(System.currentTimeMillis());
685b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    params.appendWhere("LIKE(?, " + Programs.COLUMN_CANONICAL_GENRE + ") AND "
686b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                            + Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
687b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                            + Programs.COLUMN_END_TIME_UTC_MILLIS + ">=?",
688b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                            "%" + genre + "%", curTime, curTime);
689b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                }
690b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                String inputId = uri.getQueryParameter(TvContract.PARAM_INPUT);
691b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                if (inputId != null) {
692b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    params.appendWhere(Channels.COLUMN_INPUT_ID + "=?", inputId);
693b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                }
694b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                boolean browsableOnly = uri.getBooleanQueryParameter(
695b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                        TvContract.PARAM_BROWSABLE_ONLY, false);
696b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                if (browsableOnly) {
697b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    params.appendWhere(Channels.COLUMN_BROWSABLE + "=1");
698b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                }
699bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                break;
700bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_CHANNEL_ID:
701d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                params.setTables(CHANNELS_TABLE);
702d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                params.appendWhere(Channels._ID + "=?", uri.getLastPathSegment());
703fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo                break;
704b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee            case MATCH_PROGRAM:
705d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                params.setTables(PROGRAMS_TABLE);
706b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                String paramChannelId = uri.getQueryParameter(TvContract.PARAM_CHANNEL);
707b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                if (paramChannelId != null) {
708b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    String channelId = String.valueOf(Long.parseLong(paramChannelId));
709b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    params.appendWhere(Programs.COLUMN_CHANNEL_ID + "=?", channelId);
710b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                }
711fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo                String paramStartTime = uri.getQueryParameter(TvContract.PARAM_START_TIME);
712fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo                String paramEndTime = uri.getQueryParameter(TvContract.PARAM_END_TIME);
713fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo                if (paramStartTime != null && paramEndTime != null) {
714fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo                    String startTime = String.valueOf(Long.parseLong(paramStartTime));
715fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo                    String endTime = String.valueOf(Long.parseLong(paramEndTime));
716b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                    params.appendWhere(Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
717b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee                            + Programs.COLUMN_END_TIME_UTC_MILLIS + ">=?", endTime, startTime);
718fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo                }
719fe690e3df0f514b339b3d623c148bf96a2657e67Jae Seo                break;
720bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_PROGRAM_ID:
721d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                params.setTables(PROGRAMS_TABLE);
722d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                params.appendWhere(Programs._ID + "=?", uri.getLastPathSegment());
723bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                break;
724bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_WATCHED_PROGRAM:
725d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                params.setTables(WATCHED_PROGRAMS_TABLE);
7260fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                params.appendWhere(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=?", "1");
727bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                break;
728bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            case MATCH_WATCHED_PROGRAM_ID:
729d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                params.setTables(WATCHED_PROGRAMS_TABLE);
730d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                params.appendWhere(WatchedPrograms._ID + "=?", uri.getLastPathSegment());
7310fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                params.appendWhere(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=?", "1");
732bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                break;
733916624c91fe0e53aa3e7b220b6f488ac6507285bJi-Hwan Lee            case MATCH_CHANNEL_ID_LOGO:
734b8a3d1049d6afec93fa5bcc5c8a9b0712369ed29Ji-Hwan Lee            case MATCH_PASSTHROUGH_ID:
735d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                throw new UnsupportedOperationException("Cannot " + operation + " that URI: "
736d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                        + uri);
737bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo            default:
738bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                throw new IllegalArgumentException("Unknown URI " + uri);
739bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        }
740d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        return params;
741d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    }
742bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
743d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee    private static String capitalize(String str) {
744d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        return Character.toUpperCase(str.charAt(0)) + str.substring(1);
745bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
746bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
7470fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo    @SuppressLint("DefaultLocale")
748443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee    private void checkAndConvertGenre(ContentValues values) {
749443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        String canonicalGenres = values.getAsString(Programs.COLUMN_CANONICAL_GENRE);
750443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee
751443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        if (!TextUtils.isEmpty(canonicalGenres)) {
752443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            // Check if the canonical genres are valid. If not, clear them.
753443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            String[] genres = Genres.decode(canonicalGenres);
754443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            for (String genre : genres) {
755443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                if (!Genres.isCanonical(genre)) {
756443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                    values.putNull(Programs.COLUMN_CANONICAL_GENRE);
757443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                    canonicalGenres = null;
758443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                    break;
759443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                }
760443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            }
761443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        }
762443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee
763443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        if (TextUtils.isEmpty(canonicalGenres)) {
764443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            // If the canonical genre is not set, try to map the broadcast genre to the canonical
765443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            // genre.
766443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            String broadcastGenres = values.getAsString(Programs.COLUMN_BROADCAST_GENRE);
767443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            if (!TextUtils.isEmpty(broadcastGenres)) {
768443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                Set<String> genreSet = new HashSet<String>();
769443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                String[] genres = Genres.decode(broadcastGenres);
770443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                for (String genre : genres) {
771443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                    String canonicalGenre = sGenreMap.get(genre.toUpperCase());
772443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                    if (Genres.isCanonical(canonicalGenre)) {
773443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                        genreSet.add(canonicalGenre);
774443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                    }
775443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                }
776443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                if (genreSet.size() > 0) {
777443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                    values.put(Programs.COLUMN_CANONICAL_GENRE,
778443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                            Genres.encode(genreSet.toArray(new String[0])));
779443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee                }
780443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee            }
781443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee        }
782443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee    }
783443a7feb6f6077c2309f1fb33cce72116732b43aChulwoo Lee
784fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    // We might have more than one thread trying to make its way through applyBatch() so the
785fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    // notification coalescing needs to be thread-local to work correctly.
786fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    private final ThreadLocal<Set<Uri>> mTLBatchNotifications =
787fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            new ThreadLocal<Set<Uri>>();
788fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo
789fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    private Set<Uri> getBatchNotificationsSet() {
790fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        return mTLBatchNotifications.get();
791fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    }
792fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo
793fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    private void setBatchNotificationsSet(Set<Uri> batchNotifications) {
794fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        mTLBatchNotifications.set(batchNotifications);
795fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    }
796fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo
797fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    @Override
798fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
799fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            throws OperationApplicationException {
800fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        setBatchNotificationsSet(Sets.<Uri>newHashSet());
801fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        Context context = getContext();
802fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
803fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        db.beginTransaction();
804fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        try {
805fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            ContentProviderResult[] results = super.applyBatch(operations);
806fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            db.setTransactionSuccessful();
807fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            return results;
808fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        } finally {
809fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            db.endTransaction();
810fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            final Set<Uri> notifications = getBatchNotificationsSet();
811fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            setBatchNotificationsSet(null);
812fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            for (final Uri uri : notifications) {
813fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo                context.getContentResolver().notifyChange(uri, null);
814fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            }
815fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        }
816fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    }
817fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo
818fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    private void notifyChange(Uri uri) {
819fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        final Set<Uri> batchNotifications = getBatchNotificationsSet();
820fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        if (batchNotifications != null) {
821fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            batchNotifications.add(uri);
822fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        } else {
823fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo            getContext().getContentResolver().notifyChange(uri, null);
824fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo        }
825fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo    }
826fd946b63c5b3b4b550d57fdbe9d831c77b8508a2Jae Seo
8274cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo    // When an application tries to create/read/update/delete channel or program data, we need to
8284cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo    // ensure that such an access is limited to the data entries it owns, unless it has the full
8294cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo    // access permission.
8304cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo    // Note that the user's watch log is treated with more caution and we should block any access
8314cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo    // from an application that doesn't have the proper permission.
832bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    private boolean needsToLimitPackage(Uri uri) {
833bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo        int match = sUriMatcher.match(uri);
8344cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo        if (match == MATCH_WATCHED_PROGRAM || match == MATCH_WATCHED_PROGRAM_ID) {
8354cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo            if (!callerHasAccessWatchedProgramsPermission()) {
8364cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo                throw new SecurityException("Access not allowed for " + uri);
8374cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo            }
8384cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo        }
8394cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo        return !callerHasAccessAllEpgDataPermission();
840bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
841bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
8422f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo    private boolean callerHasAccessAllEpgDataPermission() {
84313b32cf3dd4eed429f5dda7f1cad6dc50f2b5b84Jae Seo        return getContext().checkCallingOrSelfPermission(PERMISSION_ACCESS_ALL_EPG_DATA)
844bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo                == PackageManager.PERMISSION_GRANTED;
845bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo    }
846bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo
8474cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo    private boolean callerHasAccessWatchedProgramsPermission() {
8484cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo        return getContext().checkCallingOrSelfPermission(PERMISSION_ACCESS_WATCHED_PROGRAMS)
8494cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo                == PackageManager.PERMISSION_GRANTED;
8504cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo    }
8514cf2b25671ca94b824cc80b540075fb34afeae1cJae Seo
8522f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo    private boolean callerHasModifyParentalControlsPermission() {
85399c10998ff754242ba0c9cdde5de11dc373457edJae Seo        return getContext().checkCallingOrSelfPermission(
85499c10998ff754242ba0c9cdde5de11dc373457edJae Seo                android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
85599c10998ff754242ba0c9cdde5de11dc373457edJae Seo                == PackageManager.PERMISSION_GRANTED;
8562f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo    }
8572f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo
8588a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee    @Override
8598a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
8608a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        switch (sUriMatcher.match(uri)) {
8618a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            case MATCH_CHANNEL_ID_LOGO:
8628a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                return openLogoFile(uri, mode);
8638a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            default:
8648a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                throw new FileNotFoundException(uri.toString());
8658a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        }
8668a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee    }
8678a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee
8688a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee    private ParcelFileDescriptor openLogoFile(Uri uri, String mode) throws FileNotFoundException {
8698a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        long channelId = Long.parseLong(uri.getPathSegments().get(1));
870711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee
871d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        SqlParams params = new SqlParams(CHANNELS_TABLE, Channels._ID + "=?",
872d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                String.valueOf(channelId));
8732f1a3b6808ac14bc024deca6139d72a648f8b43aJae Seo        if (!callerHasAccessAllEpgDataPermission()) {
874d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            params.appendWhere(Channels.COLUMN_PACKAGE_NAME + "=?", getCallingPackage_());
875711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee        }
876711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee
877d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
878d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        queryBuilder.setTables(params.getTables());
879d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee
880711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee        // We don't write the database here.
881711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
8828a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        if (mode.equals("r")) {
883d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            String sql = queryBuilder.buildQuery(new String[] { CHANNELS_COLUMN_LOGO },
884d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                    params.getSelection(), null, null, null, null);
885d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            return DatabaseUtils.blobFileDescriptorForQuery(db, sql, params.getSelectionArgs());
8868a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        } else {
8870fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            try (Cursor cursor = queryBuilder.query(db, new String[] { Channels._ID },
8880fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    params.getSelection(), params.getSelectionArgs(), null, null, null)) {
889711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                if (cursor.getCount() < 1) {
890711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                    // Fails early if corresponding channel does not exist.
891711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                    // PipeMonitor may still fail to update DB later.
892711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                    throw new FileNotFoundException(uri.toString());
893711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                }
8948a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            }
895711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee
8968a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            try {
8978a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                ParcelFileDescriptor[] pipeFds = ParcelFileDescriptor.createPipe();
898d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                PipeMonitor pipeMonitor = new PipeMonitor(pipeFds[0], channelId, params);
8998a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                pipeMonitor.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
9008a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                return pipeFds[1];
9018a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            } catch (IOException ioe) {
902711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                FileNotFoundException fne = new FileNotFoundException(uri.toString());
9038a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                fne.initCause(ioe);
9048a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                throw fne;
9058a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            }
9068a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        }
9078a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee    }
9088a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee
9098a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee    private class PipeMonitor extends AsyncTask<Void, Void, Void> {
9108a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        private final ParcelFileDescriptor mPfd;
911711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee        private final long mChannelId;
912d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        private final SqlParams mParams;
9138a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee
914d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee        private PipeMonitor(ParcelFileDescriptor pfd, long channelId, SqlParams params) {
9158a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            mPfd = pfd;
916711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee            mChannelId = channelId;
917d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee            mParams = params;
9188a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        }
9198a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee
9208a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        @Override
9218a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        protected Void doInBackground(Void... params) {
9228a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            AutoCloseInputStream is = new AutoCloseInputStream(mPfd);
923711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee            ByteArrayOutputStream baos = null;
924711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee            int count = 0;
9258a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            try {
9268a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                Bitmap bitmap = BitmapFactory.decodeStream(is);
9278a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                if (bitmap == null) {
9288a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                    Log.e(TAG, "Failed to decode logo image for channel ID " + mChannelId);
9298a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                    return null;
9308a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                }
9318a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee
9328a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                float scaleFactor = Math.min(1f, ((float) MAX_LOGO_IMAGE_SIZE) /
9338a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                        Math.max(bitmap.getWidth(), bitmap.getHeight()));
9348a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                if (scaleFactor < 1f) {
9358a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                    bitmap = Bitmap.createScaledBitmap(bitmap,
9368a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                            (int) (bitmap.getWidth() * scaleFactor),
9378a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                            (int) (bitmap.getHeight() * scaleFactor), false);
9388a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                }
9398a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee
940711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                baos = new ByteArrayOutputStream();
941711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
942711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                byte[] bytes = baos.toByteArray();
9438a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee
944711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                ContentValues values = new ContentValues();
945711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                values.put(CHANNELS_COLUMN_LOGO, bytes);
946711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee
947711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                SQLiteDatabase db = mOpenHelper.getWritableDatabase();
948d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                count = db.update(mParams.getTables(), values, mParams.getSelection(),
949d9f937e4769668da59d614600ea3405ee0353ef6Ji-Hwan Lee                        mParams.getSelectionArgs());
950711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                if (count > 0) {
951711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                    Uri uri = TvContract.buildChannelLogoUri(mChannelId);
952711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                    notifyChange(uri);
9538a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                }
9548a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            } finally {
955711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                if (count == 0) {
956711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                    try {
957711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                        mPfd.closeWithError("Failed to write logo for channel ID " + mChannelId);
958711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                    } catch (IOException ioe) {
959711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                        Log.e(TAG, "Failed to close pipe", ioe);
960711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                    }
9618a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee                }
962711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                IoUtils.closeQuietly(baos);
963711f02f31b2be633a19fc929761581116cb0c64bJi-Hwan Lee                IoUtils.closeQuietly(is);
9648a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            }
9658a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee            return null;
9668a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee        }
9678a196b59e334924a316e6545bd877c0efc4316c8Ji-Hwan Lee    }
9680fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
9690fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo    private final void deleteUnconsolidatedWatchedProgramsRows() {
9700fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
9710fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        db.delete(WATCHED_PROGRAMS_TABLE, WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=0", null);
9720fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo    }
9730fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
9740fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo    private final class WatchLogHandler extends Handler {
9750fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        private static final int MSG_CONSOLIDATE = 1;
9760fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        private static final int MSG_TRY_CONSOLIDATE_ALL = 2;
9770fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
9780fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        @Override
9790fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        public void handleMessage(Message msg) {
9800fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            switch (msg.what) {
9810fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                case MSG_CONSOLIDATE: {
9820fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    SomeArgs args = (SomeArgs) msg.obj;
9830fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    String sessionToken = (String) args.arg1;
9840fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    long watchEndTime = (long) args.arg2;
9850fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    onConsolidate(sessionToken, watchEndTime);
9860fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    args.recycle();
9870fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    return;
9880fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                }
9890fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                case MSG_TRY_CONSOLIDATE_ALL: {
9900fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    onTryConsolidateAll();
9910fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    return;
9920fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                }
9930fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                default: {
9940fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    Log.w(TAG, "Unhandled message code: " + msg.what);
9950fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    return;
9960fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                }
9970fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
9980fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        }
9990fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
10000fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // Consolidates all WatchedPrograms rows for a given session with watch end time information
10010fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // of the most recent log entry. After this method is called, it is guaranteed that there
10020fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // remain consolidated rows only for that session.
10030fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        private final void onConsolidate(String sessionToken, long watchEndTime) {
10040fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            if (DEBUG) {
10050fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                Log.d(TAG, "onConsolidate(sessionToken=" + sessionToken + ", watchEndTime="
10060fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        + watchEndTime + ")");
10070fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
10080fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
10090fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
10100fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            queryBuilder.setTables(WATCHED_PROGRAMS_TABLE);
10110fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
10120fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
10130fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            // Pick up the last row with the same session token.
10140fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String[] projection = {
10150fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms._ID,
10160fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
10170fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms.COLUMN_CHANNEL_ID
10180fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            };
10190fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String selection = WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=? AND "
10200fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN + "=?";
10210fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String[] selectionArgs = {
10220fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    "0",
10230fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    sessionToken
10240fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            };
10250fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String sortOrder = WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + " DESC";
10260fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
10270fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            int consolidatedRowCount = 0;
10280fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            try (Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null,
10290fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    null, sortOrder)) {
10300fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                long oldWatchStartTime = watchEndTime;
10310fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                while (cursor != null && cursor.moveToNext()) {
10320fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    long id = cursor.getLong(0);
10330fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    long watchStartTime = cursor.getLong(1);
10340fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    long channelId = cursor.getLong(2);
10350fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    consolidatedRowCount += consolidateRow(id, watchStartTime, oldWatchStartTime,
10360fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                            channelId, false);
10370fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    oldWatchStartTime = watchStartTime;
10380fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                }
10390fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
10400fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            if (consolidatedRowCount > 0) {
10410fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                deleteUnsearchable();
10420fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
10430fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        }
10440fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
10450fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // Tries to consolidate all WatchedPrograms rows regardless of the session. After this
10460fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // method is called, it is guaranteed that we have at most one unconsolidated log entry per
10470fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // session that represents the user's ongoing watch activity.
10480fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // Also, this method automatically schedules the next consolidation if there still remains
10490fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // an unconsolidated entry.
10500fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        private final void onTryConsolidateAll() {
10510fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            if (DEBUG) {
10520fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                Log.d(TAG, "onTryConsolidateAll()");
10530fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
10540fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
10550fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
10560fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            queryBuilder.setTables(WATCHED_PROGRAMS_TABLE);
10570fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
10580fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
10590fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            // Pick up all unconsolidated rows grouped by session. The most recent log entry goes on
10600fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            // top.
10610fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String[] projection = {
10620fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms._ID,
10630fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
10640fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms.COLUMN_CHANNEL_ID,
10650fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN
10660fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            };
10670fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String selection = WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=0";
10680fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String sortOrder = WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN + " DESC,"
10690fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + " DESC";
10700fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
10710fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            int consolidatedRowCount = 0;
10720fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            try (Cursor cursor = queryBuilder.query(db, projection, selection, null, null, null,
10730fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    sortOrder)) {
10740fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                long oldWatchStartTime = 0;
10750fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                String oldSessionToken = null;
10760fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                while (cursor != null && cursor.moveToNext()) {
10770fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    long id = cursor.getLong(0);
10780fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    long watchStartTime = cursor.getLong(1);
10790fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    long channelId = cursor.getLong(2);
10800fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    String sessionToken = cursor.getString(3);
10810fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
10820fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    if (!sessionToken.equals(oldSessionToken)) {
10830fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        // The most recent log entry for the current session, which may be still
10840fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        // active. Just go through a dry run with the current time to see if this
10850fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        // entry can be split into multiple rows.
10860fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        consolidatedRowCount += consolidateRow(id, watchStartTime,
10870fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                                System.currentTimeMillis(), channelId, true);
10880fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        oldSessionToken = sessionToken;
10890fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    } else {
10900fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        // The later entries after the most recent one all fall into here. We now
10910fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        // know that this watch activity ended exactly at the same time when the
10920fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        // next activity started.
10930fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        consolidatedRowCount += consolidateRow(id, watchStartTime,
10940fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                                oldWatchStartTime, channelId, false);
10950fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    }
10960fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    oldWatchStartTime = watchStartTime;
10970fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                }
10980fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
10990fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            if (consolidatedRowCount > 0) {
11000fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                deleteUnsearchable();
11010fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
1102c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo            scheduleConsolidationIfNeeded();
11030fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        }
11040fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
11050fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // Consolidates a WatchedPrograms row.
11060fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // A row is 'consolidated' if and only if the following information is complete:
11070fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // 1. WatchedPrograms.COLUMN_CHANNEL_ID
11080fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // 2. WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS
11090fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // 3. WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS
11100fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // where COLUMN_WATCH_START_TIME_UTC_MILLIS <= COLUMN_WATCH_END_TIME_UTC_MILLIS.
11110fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // This is the minimal but useful enough set of information to comprise the user's watch
11120fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // history. (The program data are considered optional although we do try to fill them while
11130fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // consolidating the row.) It is guaranteed that the target row is either consolidated or
11140fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // deleted after this method is called.
11150fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // Set {@code dryRun} to {@code true} if you think it's necessary to split the row without
11160fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // consolidating the most recent row because the user stayed on the same channel for a very
11170fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // long time.
11180fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // This method returns the number of consolidated rows, which can be 0 or more.
11190fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        private final int consolidateRow(long id, long watchStartTime, long watchEndTime,
11200fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                long channelId, boolean dryRun) {
11210fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            if (DEBUG) {
11220fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                Log.d(TAG, "consolidateRow(id=" + id + ", watchStartTime=" + watchStartTime
11230fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        + ", watchEndTime=" + watchEndTime + ", channelId=" + channelId
11240fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        + ", dryRun=" + dryRun + ")");
11250fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
11260fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
11270fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
11280fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
11290fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            if (watchStartTime > watchEndTime) {
11300fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                Log.e(TAG, "watchEndTime cannot be less than watchStartTime");
11310fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                db.delete(WATCHED_PROGRAMS_TABLE, WatchedPrograms._ID + "=" + String.valueOf(id),
11320fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        null);
11330fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                return 0;
11340fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
11350fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
1136c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo            ContentValues values = getProgramValues(channelId, watchStartTime);
11370fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            Long endTime = values.getAsLong(WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS);
11380fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            boolean needsToSplit = endTime != null && endTime < watchEndTime;
11390fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
11400fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            values.put(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
11410fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    String.valueOf(watchStartTime));
11420fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            if (!dryRun || needsToSplit) {
11430fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                values.put(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
11440fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        String.valueOf(needsToSplit ? endTime : watchEndTime));
11450fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                values.put(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED, "1");
114666deb422aaf1bc5400bdcefac78d3e2c6c4f3189Sungsoo Lim                db.update(WATCHED_PROGRAMS_TABLE, values,
114766deb422aaf1bc5400bdcefac78d3e2c6c4f3189Sungsoo Lim                        WatchedPrograms._ID + "=" + String.valueOf(id), null);
114866deb422aaf1bc5400bdcefac78d3e2c6c4f3189Sungsoo Lim                // Treat the watched program is inserted when WATCHED_PROGRAMS_COLUMN_CONSOLIDATED
114966deb422aaf1bc5400bdcefac78d3e2c6c4f3189Sungsoo Lim                // becomes 1.
115066deb422aaf1bc5400bdcefac78d3e2c6c4f3189Sungsoo Lim                notifyChange(TvContract.buildWatchedProgramUri(id));
115166deb422aaf1bc5400bdcefac78d3e2c6c4f3189Sungsoo Lim            } else {
115266deb422aaf1bc5400bdcefac78d3e2c6c4f3189Sungsoo Lim                db.update(WATCHED_PROGRAMS_TABLE, values,
115366deb422aaf1bc5400bdcefac78d3e2c6c4f3189Sungsoo Lim                        WatchedPrograms._ID + "=" + String.valueOf(id), null);
11540fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
1155c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo            int count = dryRun ? 0 : 1;
11560fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            if (needsToSplit) {
11570fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                // This means that the program ended before the user stops watching the current
11580fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                // channel. In this case we duplicate the log entry as many as the number of
11590fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                // programs watched on the same channel. Here the end time of the current program
11600fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                // becomes the new watch start time of the next program.
11610fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                long duplicatedId = duplicateRow(id);
11620fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                if (duplicatedId > 0) {
11630fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    count += consolidateRow(duplicatedId, endTime, watchEndTime, channelId, dryRun);
11640fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                }
11650fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
11660fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            return count;
11670fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        }
11680fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
11690fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // Deletes the log entries from unsearchable channels. Note that only consolidated log
11700fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // entries are safe to delete.
11710fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        private final void deleteUnsearchable() {
11720fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
11730fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String deleteWhere = WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=1 AND "
11740fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + WatchedPrograms.COLUMN_CHANNEL_ID + " IN (SELECT " + Channels._ID
11750fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + " FROM " + CHANNELS_TABLE + " WHERE " + Channels.COLUMN_SEARCHABLE + "=0)";
11760fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            db.delete(WATCHED_PROGRAMS_TABLE, deleteWhere, null);
11770fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        }
11780fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
1179c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo        private final void scheduleConsolidationIfNeeded() {
1180c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo            if (DEBUG) {
1181c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                Log.d(TAG, "scheduleConsolidationIfNeeded()");
1182c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo            }
11830fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
11840fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            queryBuilder.setTables(WATCHED_PROGRAMS_TABLE);
11850fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
11860fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
11870fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            // Pick up all unconsolidated rows.
11880fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String[] projection = {
11890fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
11900fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms.COLUMN_CHANNEL_ID,
11910fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            };
11920fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String selection = WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=0";
11930fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
11940fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            try (Cursor cursor = queryBuilder.query(db, projection, selection, null, null, null,
11950fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    null)) {
1196c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                // Find the earliest time that any of the currently watching programs ends and
1197c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                // schedule the next consolidation at that time.
1198c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                long minEndTime = Long.MAX_VALUE;
1199c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                while (cursor != null && cursor.moveToNext()) {
12000fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    long watchStartTime = cursor.getLong(0);
12010fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    long channelId = cursor.getLong(1);
1202c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                    ContentValues values = getProgramValues(channelId, watchStartTime);
12030fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    Long endTime = values.getAsLong(WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS);
12040fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
12050fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    if (endTime != null && endTime < minEndTime
12060fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                            && endTime > System.currentTimeMillis()) {
12070fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                        minEndTime = endTime;
12080fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    }
12090fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                }
1210c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                if (minEndTime != Long.MAX_VALUE) {
1211c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                    sendEmptyMessageAtTime(MSG_TRY_CONSOLIDATE_ALL, minEndTime);
1212c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                    if (DEBUG) {
1213c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                        CharSequence minEndTimeStr = DateUtils.getRelativeTimeSpanString(
1214c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                                minEndTime, System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS);
1215c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                        Log.d(TAG, "onTryConsolidateAll() scheduled " + minEndTimeStr);
1216c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                    }
12170fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                }
12180fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
12190fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        }
12200fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
12210fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // Returns non-null ContentValues of the program data that the user watched on the channel
1222c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo        // {@code channelId} at the time {@code time}.
1223c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo        private final ContentValues getProgramValues(long channelId, long time) {
12240fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
12250fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            queryBuilder.setTables(PROGRAMS_TABLE);
12260fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
12270fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
12280fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String[] projection = {
12290fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    Programs.COLUMN_TITLE,
12300fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    Programs.COLUMN_START_TIME_UTC_MILLIS,
12310fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    Programs.COLUMN_END_TIME_UTC_MILLIS,
12320fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    Programs.COLUMN_SHORT_DESCRIPTION
12330fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            };
12340fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String selection = Programs.COLUMN_CHANNEL_ID + "=? AND "
12350fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
12360fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    + Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
12370fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String[] selectionArgs = {
12380fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    String.valueOf(channelId),
1239c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                    String.valueOf(time),
1240c692956369b72c8b79b9557989c46e1f85ed4dbbJae Seo                    String.valueOf(time)
12410fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            };
12420fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String sortOrder = Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
12430fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
12440fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            try (Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null,
12450fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    null, sortOrder)) {
12460fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                ContentValues values = new ContentValues();
12470fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                if (cursor != null && cursor.moveToNext()) {
12480fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    values.put(WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
12490fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    values.put(WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, cursor.getLong(1));
12500fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    values.put(WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, cursor.getLong(2));
12510fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    values.put(WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
12520fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                }
12530fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                return values;
12540fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
12550fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        }
12560fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
12570fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // Duplicates the WatchedPrograms row with a given ID and returns the ID of the duplicated
12580fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        // row. Returns -1 if failed.
12590fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        private final long duplicateRow(long id) {
12600fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            if (DEBUG) {
12610fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                Log.d(TAG, "duplicateRow(" + id + ")");
12620fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
12630fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
12640fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
12650fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            queryBuilder.setTables(WATCHED_PROGRAMS_TABLE);
12660fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
12670fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
12680fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String[] projection = {
12690fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms.COLUMN_PACKAGE_NAME,
12700fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms.COLUMN_CHANNEL_ID,
12710fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN
12720fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            };
12730fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            String selection = WatchedPrograms._ID + "=" + String.valueOf(id);
12740fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo
12750fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            try (Cursor cursor = queryBuilder.query(db, projection, selection, null, null, null,
12760fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    null)) {
12770fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                long rowId = -1;
12780fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                if (cursor != null && cursor.moveToNext()) {
12790fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    ContentValues values = new ContentValues();
12800fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    values.put(WatchedPrograms.COLUMN_PACKAGE_NAME, cursor.getString(0));
12810fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    values.put(WatchedPrograms.COLUMN_CHANNEL_ID, cursor.getLong(1));
1282f5ed20db5e1239f1a28d63ef7bed36beeaebc2e2Jae Seo                    values.put(WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN, cursor.getString(2));
12830fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                    rowId = db.insert(WATCHED_PROGRAMS_TABLE, null, values);
12840fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                }
12850fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo                return rowId;
12860fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo            }
12870fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo        }
12880fd53133eed104e0c228376e0f24194a7f6ff724Jae Seo    }
1289bbbbf738a6502a5e1d1c2d4ae4c3bb9984362b11Jae Seo}
1290