1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package androidx.tvprovider.media.tv; 17 18 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 19 20 import android.content.ContentValues; 21 import android.database.Cursor; 22 import android.os.Build; 23 24 import androidx.annotation.NonNull; 25 import androidx.annotation.RestrictTo; 26 import androidx.tvprovider.media.tv.TvContractCompat.Programs; 27 import androidx.tvprovider.media.tv.TvContractCompat.Programs.Genres.Genre; 28 29 /** 30 * A convenience class to access {@link TvContractCompat.Programs} entries in the system content 31 * provider. 32 * 33 * <p>This class makes it easy to insert or retrieve a program from the system content provider, 34 * which is defined in {@link TvContractCompat}. 35 * 36 * <p>Usage example when inserting a program: 37 * <pre> 38 * Program program = new Program.Builder() 39 * .setChannelId(channel.getId()) 40 * .setTitle("Program Title") 41 * .setDescription("Program Description") 42 * .setPosterArtUri(Uri.parse("http://example.com/poster_art.png")) 43 * // Set more attributes... 44 * .build(); 45 * Uri programUri = getContentResolver().insert(Programs.CONTENT_URI, program.toContentValues()); 46 * </pre> 47 * 48 * <p>Usage example when retrieving a program: 49 * <pre> 50 * Program program; 51 * try (Cursor cursor = resolver.query(programUri, null, null, null, null)) { 52 * if (cursor != null && cursor.getCount() != 0) { 53 * cursor.moveToNext(); 54 * program = Program.fromCursor(cursor); 55 * } 56 * } 57 * </pre> 58 * 59 * <p>Usage example when updating an existing program: 60 * <pre> 61 * Program updatedProgram = new Program.Builder(program) 62 * .setEndTimeUtcMillis(newProgramEndTime) 63 * .build(); 64 * getContentResolver().update(TvContractCompat.buildProgramUri(updatedProgram.getId()), 65 * updatedProgram.toContentValues(), null, null); 66 * </pre> 67 * 68 * <p>Usage example when deleting a program: 69 * <pre> 70 * getContentResolver().delete(TvContractCompat.buildProgramUri(existingProgram.getId()), 71 * null, null); 72 * </pre> 73 */ 74 public final class Program extends BaseProgram implements Comparable<Program> { 75 /** 76 * @hide 77 */ 78 @RestrictTo(LIBRARY_GROUP) 79 public static final String[] PROJECTION = getProjection(); 80 81 private static final long INVALID_LONG_VALUE = -1; 82 private static final int IS_RECORDING_PROHIBITED = 1; 83 84 private Program(Builder builder) { 85 super(builder); 86 } 87 88 /** 89 * @return The value of {@link Programs#COLUMN_CHANNEL_ID} for the program. 90 */ 91 public long getChannelId() { 92 Long l = mValues.getAsLong(Programs.COLUMN_CHANNEL_ID); 93 return l == null ? INVALID_LONG_VALUE : l; 94 } 95 96 /** 97 * @return The value of {@link Programs#COLUMN_START_TIME_UTC_MILLIS} for the program. 98 */ 99 public long getStartTimeUtcMillis() { 100 Long l = mValues.getAsLong(Programs.COLUMN_START_TIME_UTC_MILLIS); 101 return l == null ? INVALID_LONG_VALUE : l; 102 } 103 104 /** 105 * @return The value of {@link Programs#COLUMN_END_TIME_UTC_MILLIS} for the program. 106 */ 107 public long getEndTimeUtcMillis() { 108 Long l = mValues.getAsLong(Programs.COLUMN_END_TIME_UTC_MILLIS); 109 return l == null ? INVALID_LONG_VALUE : l; 110 } 111 112 /** 113 * @return The value of {@link Programs#COLUMN_BROADCAST_GENRE} for the program. 114 */ 115 public String[] getBroadcastGenres() { 116 return Programs.Genres.decode(mValues.getAsString(Programs.COLUMN_BROADCAST_GENRE)); 117 } 118 119 /** 120 * @return The value of {@link Programs#COLUMN_RECORDING_PROHIBITED} for the program. 121 */ 122 public boolean isRecordingProhibited() { 123 Integer i = mValues.getAsInteger(Programs.COLUMN_RECORDING_PROHIBITED); 124 return i != null && i == IS_RECORDING_PROHIBITED; 125 } 126 127 @Override 128 public int hashCode() { 129 return mValues.hashCode(); 130 } 131 132 @Override 133 public boolean equals(Object other) { 134 if (!(other instanceof Program)) { 135 return false; 136 } 137 return mValues.equals(((Program) other).mValues); 138 } 139 140 /** 141 * @param other The program you're comparing to. 142 * @return The chronological order of the programs. 143 */ 144 @Override 145 public int compareTo(@NonNull Program other) { 146 return Long.compare(mValues.getAsLong(Programs.COLUMN_START_TIME_UTC_MILLIS), 147 other.mValues.getAsLong(Programs.COLUMN_START_TIME_UTC_MILLIS)); 148 } 149 150 @Override 151 public String toString() { 152 return "Program{" + mValues.toString() + "}"; 153 } 154 155 /** 156 * @return The fields of the Program in the ContentValues format to be easily inserted into the 157 * TV Input Framework database. 158 */ 159 @Override 160 public ContentValues toContentValues() { 161 ContentValues values = super.toContentValues(); 162 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { 163 values.remove(Programs.COLUMN_RECORDING_PROHIBITED); 164 } 165 return values; 166 } 167 168 /** 169 * Creates a Program object from a cursor including the fields defined in {@link Programs}. 170 * 171 * @param cursor A row from the TV Input Framework database. 172 * @return A Program with the values taken from the cursor. 173 */ 174 public static Program fromCursor(Cursor cursor) { 175 // TODO: Add additional API which does not use costly getColumnIndex(). 176 Builder builder = new Builder(); 177 BaseProgram.setFieldsFromCursor(cursor, builder); 178 int index; 179 if ((index = cursor.getColumnIndex(Programs.COLUMN_CHANNEL_ID)) >= 0 180 && !cursor.isNull(index)) { 181 builder.setChannelId(cursor.getLong(index)); 182 } 183 if ((index = cursor.getColumnIndex(Programs.COLUMN_BROADCAST_GENRE)) >= 0 184 && !cursor.isNull(index)) { 185 builder.setBroadcastGenres(Programs.Genres.decode( 186 cursor.getString(index))); 187 } 188 if ((index = cursor.getColumnIndex(Programs.COLUMN_START_TIME_UTC_MILLIS)) >= 0 189 && !cursor.isNull(index)) { 190 builder.setStartTimeUtcMillis(cursor.getLong(index)); 191 } 192 if ((index = cursor.getColumnIndex(Programs.COLUMN_END_TIME_UTC_MILLIS)) >= 0 193 && !cursor.isNull(index)) { 194 builder.setEndTimeUtcMillis(cursor.getLong(index)); 195 } 196 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 197 if ((index = cursor.getColumnIndex(Programs.COLUMN_RECORDING_PROHIBITED)) >= 0 198 && !cursor.isNull(index)) { 199 builder.setRecordingProhibited(cursor.getInt(index) == IS_RECORDING_PROHIBITED); 200 } 201 } 202 return builder.build(); 203 } 204 205 private static String[] getProjection() { 206 String[] baseColumns = new String[] { 207 Programs.COLUMN_CHANNEL_ID, 208 Programs.COLUMN_BROADCAST_GENRE, 209 Programs.COLUMN_START_TIME_UTC_MILLIS, 210 Programs.COLUMN_END_TIME_UTC_MILLIS, 211 }; 212 String[] nougatColumns = new String[] { 213 Programs.COLUMN_RECORDING_PROHIBITED 214 }; 215 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 216 return CollectionUtils.concatAll(BaseProgram.PROJECTION, baseColumns, nougatColumns); 217 } else { 218 return CollectionUtils.concatAll(BaseProgram.PROJECTION, baseColumns); 219 } 220 } 221 222 /** 223 * This Builder class simplifies the creation of a {@link Program} object. 224 */ 225 public static class Builder extends BaseProgram.Builder<Builder> { 226 227 /** 228 * Creates a new Builder object. 229 */ 230 public Builder() { 231 } 232 233 /** 234 * Creates a new Builder object with values copied from another Program. 235 * @param other The Program you're copying from. 236 */ 237 public Builder(Program other) { 238 mValues = new ContentValues(other.mValues); 239 } 240 241 /** 242 * Sets the ID of the {@link Channel} that contains this program. 243 * 244 * @param channelId The value of {@link Programs#COLUMN_CHANNEL_ID for the program. 245 * @return This Builder object to allow for chaining of calls to builder methods. 246 */ 247 public Builder setChannelId(long channelId) { 248 mValues.put(Programs.COLUMN_CHANNEL_ID, channelId); 249 return this; 250 } 251 252 /** 253 * Sets the time when the program is going to begin in milliseconds since the epoch. 254 * 255 * @param startTimeUtcMillis The value of {@link Programs#COLUMN_START_TIME_UTC_MILLIS} for 256 * the program. 257 * @return This Builder object to allow for chaining of calls to builder methods. 258 */ 259 public Builder setStartTimeUtcMillis(long startTimeUtcMillis) { 260 mValues.put(Programs.COLUMN_START_TIME_UTC_MILLIS, startTimeUtcMillis); 261 return this; 262 } 263 264 /** 265 * Sets the time when this program is going to end in milliseconds since the epoch. 266 * 267 * @param endTimeUtcMillis The value of {@link Programs#COLUMN_END_TIME_UTC_MILLIS} for the 268 * program. 269 * @return This Builder object to allow for chaining of calls to builder methods. 270 */ 271 public Builder setEndTimeUtcMillis(long endTimeUtcMillis) { 272 mValues.put(Programs.COLUMN_END_TIME_UTC_MILLIS, endTimeUtcMillis); 273 return this; 274 } 275 276 /** 277 * Sets the broadcast-specified genres of the program. 278 * 279 * @param genres Array of genres that apply to the program based on the broadcast standard 280 * which will be flattened to a String to store in a database. 281 * @return This Builder object to allow for chaining of calls to builder methods. 282 * @see Programs#COLUMN_BROADCAST_GENRE 283 */ 284 public Builder setBroadcastGenres(@Genre String[] genres) { 285 mValues.put(Programs.COLUMN_BROADCAST_GENRE, Programs.Genres.encode(genres)); 286 return this; 287 } 288 289 /** 290 * Sets whether this program cannot be recorded. 291 * 292 * @param prohibited The value of {@link Programs#COLUMN_RECORDING_PROHIBITED} for the 293 * program. 294 * @return This Builder object to allow for chaining of calls to builder methods. 295 */ 296 public Builder setRecordingProhibited(boolean prohibited) { 297 mValues.put(Programs.COLUMN_RECORDING_PROHIBITED, 298 prohibited ? IS_RECORDING_PROHIBITED : 0); 299 return this; 300 } 301 302 /** 303 * @return A new Program with values supplied by the Builder. 304 */ 305 public Program build() { 306 return new Program(this); 307 } 308 } 309 } 310