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