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 */ 16package android.support.media.tv; 17 18import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 19 20import android.annotation.TargetApi; 21import android.content.ContentValues; 22import android.database.Cursor; 23import android.media.tv.TvContentRating; // For javadoc gen of super class 24import android.os.Build; 25import android.support.annotation.NonNull; 26import android.support.annotation.RestrictTo; 27import android.support.media.tv.TvContractCompat.Programs; 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@TargetApi(21) 75public final class Program extends BaseProgram implements Comparable<Program> { 76 /** 77 * @hide 78 */ 79 @RestrictTo(LIBRARY_GROUP) 80 public static final String[] PROJECTION = getProjection(); 81 82 private static final long INVALID_LONG_VALUE = -1; 83 private static final int IS_RECORDING_PROHIBITED = 1; 84 85 private Program(Builder builder) { 86 super(builder); 87 } 88 89 /** 90 * @return The value of {@link Programs#COLUMN_CHANNEL_ID} for the program. 91 */ 92 public long getChannelId() { 93 Long l = mValues.getAsLong(Programs.COLUMN_CHANNEL_ID); 94 return l == null ? INVALID_LONG_VALUE : l; 95 } 96 97 /** 98 * @return The value of {@link Programs#COLUMN_START_TIME_UTC_MILLIS} for the program. 99 */ 100 public long getStartTimeUtcMillis() { 101 Long l = mValues.getAsLong(Programs.COLUMN_START_TIME_UTC_MILLIS); 102 return l == null ? INVALID_LONG_VALUE : l; 103 } 104 105 /** 106 * @return The value of {@link Programs#COLUMN_END_TIME_UTC_MILLIS} for the program. 107 */ 108 public long getEndTimeUtcMillis() { 109 Long l = mValues.getAsLong(Programs.COLUMN_END_TIME_UTC_MILLIS); 110 return l == null ? INVALID_LONG_VALUE : l; 111 } 112 113 /** 114 * @return The value of {@link Programs#COLUMN_BROADCAST_GENRE} for the program. 115 */ 116 public String[] getBroadcastGenres() { 117 return Programs.Genres.decode(mValues.getAsString(Programs.COLUMN_BROADCAST_GENRE)); 118 } 119 120 /** 121 * @return The value of {@link Programs#COLUMN_RECORDING_PROHIBITED} for the program. 122 */ 123 public boolean isRecordingProhibited() { 124 Integer i = mValues.getAsInteger(Programs.COLUMN_RECORDING_PROHIBITED); 125 return i != null && i == IS_RECORDING_PROHIBITED; 126 } 127 128 @Override 129 public int hashCode() { 130 return mValues.hashCode(); 131 } 132 133 @Override 134 public boolean equals(Object other) { 135 if (!(other instanceof Program)) { 136 return false; 137 } 138 return mValues.equals(((Program) other).mValues); 139 } 140 141 /** 142 * @param other The program you're comparing to. 143 * @return The chronological order of the programs. 144 */ 145 @Override 146 public int compareTo(@NonNull Program other) { 147 return Long.compare(mValues.getAsLong(Programs.COLUMN_START_TIME_UTC_MILLIS), 148 other.mValues.getAsLong(Programs.COLUMN_START_TIME_UTC_MILLIS)); 149 } 150 151 @Override 152 public String toString() { 153 return "Program{" + mValues.toString() + "}"; 154 } 155 156 /** 157 * @return The fields of the Program in the ContentValues format to be easily inserted into the 158 * TV Input Framework database. 159 */ 160 @Override 161 public ContentValues toContentValues() { 162 ContentValues values = super.toContentValues(); 163 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { 164 values.remove(Programs.COLUMN_RECORDING_PROHIBITED); 165 } 166 return values; 167 } 168 169 /** 170 * Creates a Program object from a cursor including the fields defined in {@link Programs}. 171 * 172 * @param cursor A row from the TV Input Framework database. 173 * @return A Program with the values taken from the cursor. 174 */ 175 public static Program fromCursor(Cursor cursor) { 176 // TODO: Add additional API which does not use costly getColumnIndex(). 177 Builder builder = new Builder(); 178 BaseProgram.setFieldsFromCursor(cursor, builder); 179 int index; 180 if ((index = cursor.getColumnIndex(Programs.COLUMN_CHANNEL_ID)) >= 0 181 && !cursor.isNull(index)) { 182 builder.setChannelId(cursor.getLong(index)); 183 } 184 if ((index = cursor.getColumnIndex(Programs.COLUMN_BROADCAST_GENRE)) >= 0 185 && !cursor.isNull(index)) { 186 builder.setBroadcastGenres(Programs.Genres.decode( 187 cursor.getString(index))); 188 } 189 if ((index = cursor.getColumnIndex(Programs.COLUMN_START_TIME_UTC_MILLIS)) >= 0 190 && !cursor.isNull(index)) { 191 builder.setStartTimeUtcMillis(cursor.getLong(index)); 192 } 193 if ((index = cursor.getColumnIndex(Programs.COLUMN_END_TIME_UTC_MILLIS)) >= 0 194 && !cursor.isNull(index)) { 195 builder.setEndTimeUtcMillis(cursor.getLong(index)); 196 } 197 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 198 if ((index = cursor.getColumnIndex(Programs.COLUMN_RECORDING_PROHIBITED)) >= 0 199 && !cursor.isNull(index)) { 200 builder.setRecordingProhibited(cursor.getInt(index) == IS_RECORDING_PROHIBITED); 201 } 202 } 203 return builder.build(); 204 } 205 206 private static String[] getProjection() { 207 String[] baseColumns = new String[] { 208 Programs.COLUMN_CHANNEL_ID, 209 Programs.COLUMN_BROADCAST_GENRE, 210 Programs.COLUMN_START_TIME_UTC_MILLIS, 211 Programs.COLUMN_END_TIME_UTC_MILLIS, 212 }; 213 String[] nougatColumns = new String[] { 214 Programs.COLUMN_RECORDING_PROHIBITED 215 }; 216 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 217 return CollectionUtils.concatAll(BaseProgram.PROJECTION, baseColumns, nougatColumns); 218 } else { 219 return CollectionUtils.concatAll(BaseProgram.PROJECTION, baseColumns); 220 } 221 } 222 223 /** 224 * This Builder class simplifies the creation of a {@link Program} object. 225 */ 226 public static class Builder extends BaseProgram.Builder<Builder> { 227 228 /** 229 * Creates a new Builder object. 230 */ 231 public Builder() { 232 } 233 234 /** 235 * Creates a new Builder object with values copied from another Program. 236 * @param other The Program you're copying from. 237 */ 238 public Builder(Program other) { 239 mValues = new ContentValues(other.mValues); 240 } 241 242 /** 243 * Sets the ID of the {@link Channel} that contains this program. 244 * 245 * @param channelId The value of {@link Programs#COLUMN_CHANNEL_ID for the program. 246 * @return This Builder object to allow for chaining of calls to builder methods. 247 */ 248 public Builder setChannelId(long channelId) { 249 mValues.put(Programs.COLUMN_CHANNEL_ID, channelId); 250 return this; 251 } 252 253 /** 254 * Sets the time when the program is going to begin in milliseconds since the epoch. 255 * 256 * @param startTimeUtcMillis The value of {@link Programs#COLUMN_START_TIME_UTC_MILLIS} for 257 * the program. 258 * @return This Builder object to allow for chaining of calls to builder methods. 259 */ 260 public Builder setStartTimeUtcMillis(long startTimeUtcMillis) { 261 mValues.put(Programs.COLUMN_START_TIME_UTC_MILLIS, startTimeUtcMillis); 262 return this; 263 } 264 265 /** 266 * Sets the time when this program is going to end in milliseconds since the epoch. 267 * 268 * @param endTimeUtcMillis The value of {@link Programs#COLUMN_END_TIME_UTC_MILLIS} for the 269 * program. 270 * @return This Builder object to allow for chaining of calls to builder methods. 271 */ 272 public Builder setEndTimeUtcMillis(long endTimeUtcMillis) { 273 mValues.put(Programs.COLUMN_END_TIME_UTC_MILLIS, endTimeUtcMillis); 274 return this; 275 } 276 277 /** 278 * Sets the broadcast-specified genres of the program. 279 * 280 * @param genres Array of genres that apply to the program based on the broadcast standard 281 * which will be flattened to a String to store in a database. 282 * @return This Builder object to allow for chaining of calls to builder methods. 283 * @see Programs#COLUMN_BROADCAST_GENRE 284 */ 285 public Builder setBroadcastGenres(String[] genres) { 286 mValues.put(Programs.COLUMN_BROADCAST_GENRE, Programs.Genres.encode(genres)); 287 return this; 288 } 289 290 /** 291 * Sets whether this program cannot be recorded. 292 * 293 * @param prohibited The value of {@link Programs#COLUMN_RECORDING_PROHIBITED} for the 294 * program. 295 * @return This Builder object to allow for chaining of calls to builder methods. 296 */ 297 public Builder setRecordingProhibited(boolean prohibited) { 298 mValues.put(Programs.COLUMN_RECORDING_PROHIBITED, 299 prohibited ? IS_RECORDING_PROHIBITED : 0); 300 return this; 301 } 302 303 /** 304 * @return A new Program with values supplied by the Builder. 305 */ 306 public Program build() { 307 return new Program(this); 308 } 309 } 310} 311