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 androidx.tvprovider.media.tv; 17 18import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 19 20import android.content.ContentValues; 21import android.database.Cursor; 22import android.os.Build; 23 24import androidx.annotation.IntDef; 25import androidx.annotation.RestrictTo; 26import androidx.tvprovider.media.tv.TvContractCompat.WatchNextPrograms; 27 28import java.lang.annotation.Retention; 29import java.lang.annotation.RetentionPolicy; 30import java.util.Objects; 31import java.util.Set; 32 33/** 34 * A convenience class to access {@link WatchNextPrograms} entries in the system content 35 * provider. 36 * 37 * <p>This class makes it easy to insert or retrieve a program from the system content provider, 38 * which is defined in {@link TvContractCompat}. 39 * 40 * <p>Usage example when inserting a "watch next" program: 41 * <pre> 42 * WatchNextProgram watchNextProgram = new WatchNextProgram.Builder() 43 * .setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE) 44 * .setType(PreviewPrograms.TYPE_MOVIE) 45 * .setTitle("Program Title") 46 * .setDescription("Program Description") 47 * .setPosterArtUri(Uri.parse("http://example.com/poster_art.png")) 48 * // Set more attributes... 49 * .build(); 50 * Uri watchNextProgramUri = getContentResolver().insert(WatchNextPrograms.CONTENT_URI, 51 * watchNextProgram.toContentValues()); 52 * </pre> 53 * 54 * <p>Usage example when retrieving a "watch next" program: 55 * <pre> 56 * WatchNextProgram watchNextProgram; 57 * try (Cursor cursor = resolver.query(watchNextProgramUri, null, null, null, null)) { 58 * if (cursor != null && cursor.getCount() != 0) { 59 * cursor.moveToNext(); 60 * watchNextProgram = WatchNextProgram.fromCursor(cursor); 61 * } 62 * } 63 * </pre> 64 * 65 * <p>Usage example when updating an existing "watch next" program: 66 * <pre> 67 * WatchNextProgram updatedProgram = new WatchNextProgram.Builder(watchNextProgram) 68 * .setLastEngagementTimeUtcMillis(System.currentTimeMillis()) 69 * .build(); 70 * getContentResolver().update(TvContractCompat.buildWatchNextProgramUri(updatedProgram.getId()), 71 * updatedProgram.toContentValues(), null, null); 72 * </pre> 73 * 74 * <p>Usage example when deleting a "watch next" program: 75 * <pre> 76 * getContentResolver().delete(TvContractCompat.buildWatchNextProgramUri(existingProgram.getId()), 77 * null, null); 78 * </pre> 79 */ 80public final class WatchNextProgram extends BasePreviewProgram { 81 /** 82 * @hide 83 */ 84 @RestrictTo(LIBRARY_GROUP) 85 public static final String[] PROJECTION = getProjection(); 86 87 private static final long INVALID_LONG_VALUE = -1; 88 private static final int INVALID_INT_VALUE = -1; 89 90 /** @hide */ 91 @IntDef({ 92 WATCH_NEXT_TYPE_UNKNOWN, 93 WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE, 94 WatchNextPrograms.WATCH_NEXT_TYPE_NEXT, 95 WatchNextPrograms.WATCH_NEXT_TYPE_NEW, 96 WatchNextPrograms.WATCH_NEXT_TYPE_WATCHLIST, 97 }) 98 @Retention(RetentionPolicy.SOURCE) 99 @RestrictTo(LIBRARY_GROUP) 100 public @interface WatchNextType { 101 } 102 103 /** 104 * The unknown watch next type. Use this type when the actual type is not known. 105 */ 106 public static final int WATCH_NEXT_TYPE_UNKNOWN = -1; 107 108 private WatchNextProgram(Builder builder) { 109 super(builder); 110 } 111 112 /** 113 * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program, 114 * or {@link #WATCH_NEXT_TYPE_UNKNOWN} if it's unknown. 115 */ 116 public @WatchNextType int getWatchNextType() { 117 Integer i = mValues.getAsInteger(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE); 118 return i == null ? WATCH_NEXT_TYPE_UNKNOWN : i; 119 } 120 121 /** 122 * @return The value of {@link WatchNextPrograms#COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS} for the 123 * program. 124 */ 125 public long getLastEngagementTimeUtcMillis() { 126 Long l = mValues.getAsLong(WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS); 127 return l == null ? INVALID_LONG_VALUE : l; 128 } 129 130 @Override 131 public boolean equals(Object other) { 132 if (!(other instanceof WatchNextProgram)) { 133 return false; 134 } 135 return mValues.equals(((WatchNextProgram) other).mValues); 136 } 137 138 /** 139 * Indicates whether some other WatchNextProgram has any set attribute that is different from 140 * this WatchNextProgram's respective attributes. An attribute is considered "set" if its key 141 * is present in the ContentValues vector. 142 */ 143 public boolean hasAnyUpdatedValues(WatchNextProgram update) { 144 Set<String> updateKeys = update.mValues.keySet(); 145 for (String key : updateKeys) { 146 Object updateValue = update.mValues.get(key); 147 Object currValue = mValues.get(key); 148 if (!Objects.deepEquals(updateValue, currValue)) { 149 return true; 150 } 151 } 152 return false; 153 } 154 155 @Override 156 public String toString() { 157 return "WatchNextProgram{" + mValues.toString() + "}"; 158 } 159 160 /** 161 * @return The fields of the Program in the ContentValues format to be easily inserted into the 162 * TV Input Framework database. 163 */ 164 @Override 165 public ContentValues toContentValues() { 166 return toContentValues(false); 167 } 168 169 /** 170 * Returns fields of the WatchNextProgram in the ContentValues format to be easily inserted 171 * into the TV Input Framework database. 172 * 173 * @param includeProtectedFields Whether the fields protected by system is included or not. 174 * @hide 175 */ 176 @RestrictTo(LIBRARY_GROUP) 177 @Override 178 public ContentValues toContentValues(boolean includeProtectedFields) { 179 ContentValues values = super.toContentValues(includeProtectedFields); 180 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { 181 values.remove(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE); 182 values.remove(WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS); 183 } 184 return values; 185 } 186 187 /** 188 * Creates a WatchNextProgram object from a cursor including the fields defined in 189 * {@link WatchNextPrograms}. 190 * 191 * @param cursor A row from the TV Input Framework database. 192 * @return A Program with the values taken from the cursor. 193 */ 194 public static WatchNextProgram fromCursor(Cursor cursor) { 195 // TODO: Add additional API which does not use costly getColumnIndex(). 196 Builder builder = new Builder(); 197 BasePreviewProgram.setFieldsFromCursor(cursor, builder); 198 int index; 199 if ((index = cursor.getColumnIndex(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE)) >= 0 200 && !cursor.isNull(index)) { 201 builder.setWatchNextType(cursor.getInt(index)); 202 } 203 if ((index = cursor.getColumnIndex( 204 WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS)) >= 0 205 && !cursor.isNull(index)) { 206 builder.setLastEngagementTimeUtcMillis(cursor.getLong(index)); 207 } 208 return builder.build(); 209 } 210 211 private static String[] getProjection() { 212 String[] oColumns = new String[]{ 213 WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE, 214 WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS, 215 }; 216 return CollectionUtils.concatAll(BasePreviewProgram.PROJECTION, oColumns); 217 } 218 219 /** 220 * This Builder class simplifies the creation of a {@link WatchNextProgram} object. 221 */ 222 public static final class Builder extends BasePreviewProgram.Builder<Builder> { 223 224 /** 225 * Creates a new Builder object. 226 */ 227 public Builder() { 228 } 229 230 /** 231 * Creates a new Builder object with values copied from another Program. 232 * 233 * @param other The Program you're copying from. 234 */ 235 public Builder(WatchNextProgram other) { 236 mValues = new ContentValues(other.mValues); 237 } 238 239 /** 240 * Sets the "watch next" type of this program content. 241 * 242 * <p>The value should match one of the followings: 243 * {@link WatchNextPrograms#WATCH_NEXT_TYPE_CONTINUE}, 244 * {@link WatchNextPrograms#WATCH_NEXT_TYPE_NEXT}, and 245 * {@link WatchNextPrograms#WATCH_NEXT_TYPE_NEW}. 246 * 247 * @param watchNextType The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for 248 * the program. 249 * @return This Builder object to allow for chaining of calls to builder methods. 250 */ 251 public Builder setWatchNextType(@WatchNextType int watchNextType) { 252 mValues.put(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE, watchNextType); 253 return this; 254 } 255 256 /** 257 * Sets the time when the program is going to begin in milliseconds since the epoch. 258 * 259 * @param lastEngagementTimeUtcMillis The value of 260 * {@link WatchNextPrograms#COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS} 261 * for the program. 262 * @return This Builder object to allow for chaining of calls to builder methods. 263 */ 264 public Builder setLastEngagementTimeUtcMillis(long lastEngagementTimeUtcMillis) { 265 mValues.put(WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS, 266 lastEngagementTimeUtcMillis); 267 return this; 268 } 269 270 /** 271 * @return A new Program with values supplied by the Builder. 272 */ 273 public WatchNextProgram build() { 274 return new WatchNextProgram(this); 275 } 276 } 277} 278