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