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