1/*
2 * Copyright (C) 2015 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
17package android.support.app.recommendation;
18
19import android.app.Notification;
20import android.app.PendingIntent;
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.Bitmap;
24import android.os.Bundle;
25import android.support.annotation.ColorInt;
26import android.support.annotation.DrawableRes;
27import android.support.annotation.IntDef;
28import android.support.annotation.Nullable;
29import android.support.annotation.StringDef;
30import android.text.TextUtils;
31
32import java.util.Arrays;
33
34/**
35 * The ContentRecommendation object encapsulates all application provided data for a single content
36 * recommendation item.
37 */
38public final class ContentRecommendation
39{
40    @StringDef({
41        CONTENT_TYPE_VIDEO,
42        CONTENT_TYPE_MOVIE,
43        CONTENT_TYPE_TRAILER,
44        CONTENT_TYPE_SERIAL,
45        CONTENT_TYPE_MUSIC,
46        CONTENT_TYPE_RADIO,
47        CONTENT_TYPE_PODCAST,
48        CONTENT_TYPE_NEWS,
49        CONTENT_TYPE_SPORTS,
50        CONTENT_TYPE_APP,
51        CONTENT_TYPE_GAME,
52        CONTENT_TYPE_BOOK,
53        CONTENT_TYPE_COMIC,
54        CONTENT_TYPE_MAGAZINE,
55        CONTENT_TYPE_WEBSITE,
56    })
57    public @interface ContentType {}
58
59    /**
60     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
61     * by the notification item is a video clip.
62     */
63    public static final String CONTENT_TYPE_VIDEO = "android.contentType.video";
64
65    /**
66     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
67     * by the notification item is a movie.
68     */
69    public static final String CONTENT_TYPE_MOVIE = "android.contentType.movie";
70
71    /**
72     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
73     * by the notification item is a trailer.
74     */
75    public static final String CONTENT_TYPE_TRAILER = "android.contentType.trailer";
76
77    /**
78     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
79     * by the notification item is serial. It can refer to an entire show, a single season or
80     * series, or a single episode.
81     */
82    public static final String CONTENT_TYPE_SERIAL = "android.contentType.serial";
83
84    /**
85     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
86     * by the notification item is a song or album.
87     */
88    public static final String CONTENT_TYPE_MUSIC = "android.contentType.music";
89
90    /**
91     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
92     * by the notification item is a radio station.
93     */
94    public static final String CONTENT_TYPE_RADIO = "android.contentType.radio";
95
96    /**
97     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
98     * by the notification item is a podcast.
99     */
100    public static final String CONTENT_TYPE_PODCAST = "android.contentType.podcast";
101
102    /**
103     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
104     * by the notification item is a news item.
105     */
106    public static final String CONTENT_TYPE_NEWS = "android.contentType.news";
107
108    /**
109     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
110     * by the notification item is sports.
111     */
112    public static final String CONTENT_TYPE_SPORTS = "android.contentType.sports";
113
114    /**
115     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
116     * by the notification item is an application.
117     */
118    public static final String CONTENT_TYPE_APP = "android.contentType.app";
119
120    /**
121     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
122     * by the notification item is a game.
123     */
124    public static final String CONTENT_TYPE_GAME = "android.contentType.game";
125
126    /**
127     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
128     * by the notification item is a book.
129     */
130    public static final String CONTENT_TYPE_BOOK = "android.contentType.book";
131
132    /**
133     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
134     * by the notification item is a comic book.
135     */
136    public static final String CONTENT_TYPE_COMIC = "android.contentType.comic";
137
138    /**
139     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
140     * by the notification item is a magazine.
141     */
142    public static final String CONTENT_TYPE_MAGAZINE = "android.contentType.magazine";
143
144    /**
145     * Value to be used with {@link Builder#setContentTypes} to indicate that the content referred
146     * by the notification item is a website.
147     */
148    public static final String CONTENT_TYPE_WEBSITE = "android.contentType.website";
149
150    @StringDef({
151        CONTENT_PRICING_FREE,
152        CONTENT_PRICING_RENTAL,
153        CONTENT_PRICING_PURCHASE,
154        CONTENT_PRICING_PREORDER,
155        CONTENT_PRICING_SUBSCRIPTION,
156    })
157    public @interface ContentPricing {}
158
159    /**
160     * Value to be used with {@link Builder#setPricingInformation} to indicate that the content
161     * referred by the notification item is free to consume.
162     */
163    public static final String CONTENT_PRICING_FREE = "android.contentPrice.free";
164
165    /**
166     * Value to be used with {@link Builder#setPricingInformation} to indicate that the content
167     * referred by the notification item is available as a rental, and the price value provided is
168     * the rental price for the item.
169     */
170    public static final String CONTENT_PRICING_RENTAL = "android.contentPrice.rental";
171
172    /**
173     * Value to be used with {@link Builder#setPricingInformation} to indicate that the content
174     * referred by the notification item is available for purchase, and the price value provided is
175     * the purchase price for the item.
176     */
177    public static final String CONTENT_PRICING_PURCHASE = "android.contentPrice.purchase";
178
179    /**
180     * Value to be used with {@link Builder#setPricingInformation} to indicate that the content
181     * referred by the notification item is available currently as a pre-order, and the price value
182     * provided is the purchase price for the item.
183     */
184    public static final String CONTENT_PRICING_PREORDER = "android.contentPrice.preorder";
185
186    /**
187     * Value to be used with {@link Builder#setPricingInformation} to indicate that the content
188     * referred by the notification item is available as part of a subscription based service, and
189     * the price value provided is the subscription price for the service.
190     */
191    public static final String CONTENT_PRICING_SUBSCRIPTION =
192            "android.contentPrice.subscription";
193
194    @IntDef({
195        CONTENT_STATUS_READY,
196        CONTENT_STATUS_PENDING,
197        CONTENT_STATUS_AVAILABLE,
198        CONTENT_STATUS_UNAVAILABLE,
199    })
200    public @interface ContentStatus {}
201
202    /**
203     * Value to be used with {@link Builder#setStatus} to indicate that the content referred by the
204     * notification is available and ready to be consumed immediately.
205     */
206    public static final int CONTENT_STATUS_READY = 0;
207
208    /**
209     * Value to be used with {@link Builder#setStatus} to indicate that the content referred by the
210     * notification is pending, waiting on either a download or purchase operation to complete
211     * before it can be consumed.
212     */
213    public static final int CONTENT_STATUS_PENDING = 1;
214
215    /**
216     * Value to be used with {@link Builder#setStatus} to indicate that the content referred by the
217     * notification is available, but needs to be first purchased, rented, subscribed or downloaded
218     * before it can be consumed.
219     */
220    public static final int CONTENT_STATUS_AVAILABLE = 2;
221
222    /**
223     * Value to be used with {@link Builder#setStatus} to indicate that the content referred by the
224     * notification is not available. This could be content not available in a certain region or
225     * incompatible with the device in use.
226     */
227    public static final int CONTENT_STATUS_UNAVAILABLE = 3;
228
229    @StringDef({
230        CONTENT_MATURITY_ALL,
231        CONTENT_MATURITY_LOW,
232        CONTENT_MATURITY_MEDIUM,
233        CONTENT_MATURITY_HIGH,
234    })
235    public @interface ContentMaturity {}
236
237    /**
238     * Value to be used with {@link Builder#setMaturityRating} to indicate that the content referred
239     * by the notification is suitable for all audiences.
240     */
241    public static final String CONTENT_MATURITY_ALL = "android.contentMaturity.all";
242
243    /**
244     * Value to be used with {@link Builder#setMaturityRating} to indicate that the content referred
245     * by the notification is suitable for audiences of low maturity and above.
246     */
247    public static final String CONTENT_MATURITY_LOW = "android.contentMaturity.low";
248
249    /**
250     * Value to be used with {@link Builder#setMaturityRating} to indicate that the content referred
251     * by the notification is suitable for audiences of medium maturity and above.
252     */
253    public static final String CONTENT_MATURITY_MEDIUM = "android.contentMaturity.medium";
254
255    /**
256     * Value to be used with {@link Builder#setMaturityRating} to indicate that the content referred
257     * by the notification is suitable for audiences of high maturity and above.
258     */
259    public static final String CONTENT_MATURITY_HIGH = "android.contentMaturity.high";
260
261    @IntDef({
262            INTENT_TYPE_ACTIVITY,
263            INTENT_TYPE_BROADCAST,
264            INTENT_TYPE_SERVICE,
265    })
266    public @interface IntentType {
267    }
268
269    /**
270     * Value to be used with {@link Builder#setContentIntentData} and
271     * {@link Builder#setDismissIntentData} to indicate that a {@link PendingIntent} for an Activity
272     * should be created when posting the recommendation to the HomeScreen.
273     */
274    public static final int INTENT_TYPE_ACTIVITY = 1;
275
276    /**
277     * Value to be used with {@link Builder#setContentIntentData} and
278     * {@link Builder#setDismissIntentData} to indicate that a {@link PendingIntent} for a Broadcast
279     * should be created when posting the recommendation to the HomeScreen.
280     */
281    public static final int INTENT_TYPE_BROADCAST = 2;
282
283    /**
284     * Value to be used with {@link Builder#setContentIntentData} and
285     * {@link Builder#setDismissIntentData} to indicate that a {@link PendingIntent} for a Service
286     * should be created when posting the recommendation to the HomeScreen.
287     */
288    public static final int INTENT_TYPE_SERVICE = 3;
289
290    /**
291     * Object used to encapsulate the data to be used to build the {@link PendingIntent} object
292     * associated with a given content recommendation, at the time this recommendation gets posted
293     * to the home Screen.
294     * <p>
295     * The members of this object correspond to the fields passed into the {@link PendingIntent}
296     * factory methods, when creating a new PendingIntent.
297     */
298    public static class IntentData {
299        int mType;
300        Intent mIntent;
301        int mRequestCode;
302        Bundle mOptions;
303    }
304
305    private final String mIdTag;
306    private final String mTitle;
307    private final String mText;
308    private final String mSourceName;
309    private final Bitmap mContentImage;
310    private final int mBadgeIconId;
311    private final String mBackgroundImageUri;
312    private final int mColor;
313    private final IntentData mContentIntentData;
314    private final IntentData mDismissIntentData;
315    private final String[] mContentTypes;
316    private final String[] mContentGenres;
317    private final String mPriceType;
318    private final String mPriceValue;
319    private final String mMaturityRating;
320    private final long mRunningTime;
321
322    // Mutable fields
323    private String mGroup;
324    private String mSortKey;
325    private int mProgressAmount;
326    private int mProgressMax;
327    private boolean mAutoDismiss;
328    private int mStatus;
329
330    private ContentRecommendation(Builder builder) {
331        mIdTag = builder.mBuilderIdTag;
332        mTitle = builder.mBuilderTitle;
333        mText = builder.mBuilderText;
334        mSourceName = builder.mBuilderSourceName;
335        mContentImage = builder.mBuilderContentImage;
336        mBadgeIconId = builder.mBuilderBadgeIconId;
337        mBackgroundImageUri = builder.mBuilderBackgroundImageUri;
338        mColor = builder.mBuilderColor;
339        mContentIntentData = builder.mBuilderContentIntentData;
340        mDismissIntentData = builder.mBuilderDismissIntentData;
341        mContentTypes = builder.mBuilderContentTypes;
342        mContentGenres = builder.mBuilderContentGenres;
343        mPriceType = builder.mBuilderPriceType;
344        mPriceValue = builder.mBuilderPriceValue;
345        mMaturityRating = builder.mBuilderMaturityRating;
346        mRunningTime = builder.mBuilderRunningTime;
347
348        mGroup = builder.mBuilderGroup;
349        mSortKey = builder.mBuilderSortKey;
350        mProgressAmount = builder.mBuilderProgressAmount;
351        mProgressMax = builder.mBuilderProgressMax;
352        mAutoDismiss = builder.mBuilderAutoDismiss;
353        mStatus = builder.mBuilderStatus;
354    }
355
356    /**
357     * Returns the String Id tag which uniquely identifies this recommendation.
358     *
359     * @return The String Id tag for this recommendation.
360     */
361    public String getIdTag() {
362        return mIdTag;
363    }
364
365    /**
366     * Returns the content title for this recommendation.
367     *
368     * @return A String containing the recommendation content title.
369     */
370    public String getTitle() {
371        return mTitle;
372    }
373
374    /**
375     * Returns the description text for this recommendation.
376     *
377     * @return A String containing the recommendation description text.
378     */
379    public String getText() {
380        return mText;
381    }
382
383    /**
384     * Returns the source application name for this recommendation.
385     *
386     * @return A String containing the recommendation source name.
387     */
388    public String getSourceName() {
389        return mSourceName;
390    }
391
392    /**
393     * Returns the Bitmap containing the recommendation image.
394     *
395     * @return A Bitmap containing the recommendation image.
396     */
397    public Bitmap getContentImage() {
398        return mContentImage;
399    }
400
401    /**
402     * Returns the resource id for the recommendation badging icon.
403     * <p>
404     * The resource id represents the icon resource in the source application package.
405     *
406     * @return An integer id for the badge icon resource.
407     */
408    public int getBadgeImageResourceId() {
409        return mBadgeIconId;
410    }
411
412    /**
413     * Returns a Content URI that can be used to retrieve the background image for this
414     * recommendation.
415     *
416     * @return A Content URI pointing to the recommendation background image.
417     */
418    public String getBackgroundImageUri() {
419        return mBackgroundImageUri;
420    }
421
422    /**
423     * Returns the accent color value to be used in the UI when displaying this content
424     * recommendation to the user.
425     *
426     * @return An integer value representing the accent color for this recommendation.
427     */
428    public int getColor() {
429        return mColor;
430    }
431
432    /**
433     * Sets the String group ID tag for this recommendation.
434     * <p>
435     * Recommendations in the same group are ranked by the Home Screen together, and the sort order
436     * within a group is respected. This can be useful if the application has different sources for
437     * recommendations, like "trending", "subscriptions", and "new music" categories for YouTube,
438     * where the user can be more interested in recommendations from one group than another.
439     *
440     * @param groupTag A String containing the group ID tag for this recommendation.
441     */
442    public void setGroup(String groupTag) {
443        mGroup = groupTag;
444    }
445
446    /**
447     * Returns the String group ID tag for this recommendation.
448     *
449     * @return A String containing the group ID tag for this recommendation.
450     */
451    public String getGroup() {
452        return mGroup;
453    }
454
455    /**
456     * Sets the String sort key for this recommendation.
457     * <p>
458     * The sort key must be a String representation of a float number between 0.0 and 1.0, and is
459     * used to indicate the relative importance (and sort order) of a single recommendation within
460     * its specified group. The recommendations will be ordered in decreasing order of importance
461     * within a given group.
462     *
463     * @param sortKey A String containing the sort key for this recommendation.
464     */
465    public void setSortKey(String sortKey) {
466        mSortKey = sortKey;
467    }
468
469    /**
470     * Returns the String sort key for this recommendation.
471     *
472     * @return A String containing the sort key for this recommendation.
473     */
474    public String getSortKey() {
475        return mSortKey;
476    }
477
478    /**
479     * Sets the progress information for the content pointed to by this recommendation.
480     *
481     * @param max The maximum value for the progress of this content.
482     * @param progress The progress amount for this content. Must be in the range (0 - max).
483     */
484    public void setProgress(int max, int progress) {
485        if (max < 0 || progress < 0) {
486            throw new IllegalArgumentException();
487        }
488        mProgressMax = max;
489        mProgressAmount = progress;
490    }
491
492    /**
493     * Indicates if this recommendation contains valid progress information.
494     *
495     * @return true if the recommendation contains valid progress data, false otherwise.
496     */
497    public boolean hasProgressInfo() {
498        return mProgressMax != 0;
499    }
500
501    /**
502     * Returns the maximum value for the progress data of this recommendation.
503     *
504     * @return An integer representing the maximum progress value.
505     */
506    public int getProgressMax() {
507        return mProgressMax;
508    }
509
510    /**
511     * Returns the progress amount for this recommendation.
512     *
513     * @return An integer representing the recommendation progress amount.
514     */
515    public int getProgressValue() {
516        return mProgressAmount;
517    }
518
519    /**
520     * Sets the flag indicating if this recommendation should be dismissed automatically.
521     * <p>
522     * Auto-dismiss notifications are automatically removed from the Home Screen when the user
523     * clicks on them.
524     *
525     * @param autoDismiss A boolean indicating if the recommendation should be auto dismissed or
526     *            not.
527     */
528    public void setAutoDismiss(boolean autoDismiss) {
529        mAutoDismiss = autoDismiss;
530    }
531
532    /**
533     * Indicates whether this recommendation should be dismissed automatically.
534     * <p>
535     * Auto-dismiss notifications are automatically removed from the Home Screen when the user
536     * clicks on them.
537     *
538     * @return true if the recommendation is marked for auto dismissal, or false otherwise.
539     */
540    public boolean isAutoDismiss() {
541        return mAutoDismiss;
542    }
543
544    /**
545     * Returns the data for the Intent that will be issued when the user clicks on the
546     * recommendation.
547     *
548     * @return An IntentData object, containing the data for the Intent that gets issued when the
549     *         recommendation is clicked on.
550     */
551    public IntentData getContentIntent() {
552        return mContentIntentData;
553    }
554
555    /**
556     * Returns the data for the Intent that will be issued when the recommendation gets dismissed
557     * from the Home Screen, due to an user action.
558     *
559     * @return An IntentData object, containing the data for the Intent that gets issued when the
560     *         recommendation is dismissed from the Home Screen.
561     */
562    public IntentData getDismissIntent() {
563        return mDismissIntentData;
564    }
565
566    /**
567     * Returns an array containing the content types tags that describe the content. The first tag
568     * entry is considered the primary type for the content, and is used for content ranking
569     * purposes.
570     *
571     * @return An array of predefined type tags (see the <code>CONTENT_TYPE_*</code> constants) that
572     *         describe the recommended content.
573     */
574    public String[] getContentTypes() {
575        if (mContentTypes != null) {
576            return Arrays.copyOf(mContentTypes, mContentTypes.length);
577        }
578        return mContentTypes;
579    }
580
581    /**
582     * Returns the primary content type tag for the recommendation, or null if no content types have
583     * been specified.
584     *
585     * @return A predefined type tag (see the <code>CONTENT_TYPE_*</code> constants) indicating the
586     *         primary content type for the recommendation.
587     */
588    public String getPrimaryContentType() {
589        if (mContentTypes != null && mContentTypes.length > 0) {
590            return mContentTypes[0];
591        }
592        return null;
593    }
594
595    /**
596     * Returns an array containing the genres that describe the content. Genres are open ended
597     * String tags.
598     *
599     * @return An array of genre tags that describe the recommended content.
600     */
601    public String[] getGenres() {
602        if (mContentGenres != null) {
603            return Arrays.copyOf(mContentGenres, mContentGenres.length);
604        }
605        return mContentGenres;
606    }
607
608    /**
609     * Gets the pricing type for the content.
610     *
611     * @return A predefined tag indicating the pricing type for the content (see the <code>
612     *         CONTENT_PRICING_*</code> constants).
613     */
614    public String getPricingType() {
615        return mPriceType;
616    }
617
618    /**
619     * Gets the price value (when applicable) for the content. The value will be provided as a
620     * String containing the price in the appropriate currency for the current locale.
621     *
622     * @return A string containing a representation of the content price in the current locale and
623     *         currency.
624     */
625    public String getPricingValue() {
626        return mPriceValue;
627    }
628
629    /**
630     * Sets the availability status value for the content. This status indicates whether the content
631     * is ready to be consumed on the device, or if the user must first purchase, rent, subscribe
632     * to, or download the content.
633     *
634     * @param status The status value for the content. (see the <code>CONTENT_STATUS_*</code> for
635     *            the valid status values).
636     */
637    public void setStatus(@ContentStatus int status) {
638        mStatus = status;
639    }
640
641    /**
642     * Returns availability status value for the content. This status indicates whether the content
643     * is ready to be consumed on the device, or if the user must first purchase, rent, subscribe
644     * to, or download the content.
645     *
646     * @return The status value for the content, or -1 is a valid status has not been specified (see
647     *         the <code>CONTENT_STATUS_*</code> constants for the valid status values).
648     */
649    public int getStatus() {
650        return mStatus;
651    }
652
653    /**
654     * Returns the maturity level rating for the content.
655     *
656     * @return returns a predefined tag indicating the maturity level rating for the content (see
657     *         the <code>CONTENT_MATURITY_*</code> constants).
658     */
659    public String getMaturityRating() {
660        return mMaturityRating;
661    }
662
663    /**
664     * Returns the running time for the content.
665     *
666     * @return The run length, in seconds, of the content associated with the notification.
667     */
668    public long getRunningTime() {
669        return mRunningTime;
670    }
671
672    @Override
673    public boolean equals(Object other) {
674        if (other instanceof ContentRecommendation) {
675            return TextUtils.equals(mIdTag, ((ContentRecommendation) other).getIdTag());
676        }
677        return false;
678    }
679
680    @Override
681    public int hashCode() {
682        if (mIdTag != null) {
683            return mIdTag.hashCode();
684        }
685        return Integer.MAX_VALUE;
686    }
687
688    /**
689     * Builder class for {@link ContentRecommendation} objects. Provides a convenient way to set the
690     * various fields of a {@link ContentRecommendation}.
691     * <p>
692     * Example:
693     *
694     * <pre class="prettyprint">
695     * ContentRecommendation rec = new ContentRecommendation.Builder()
696     *         .setIdInfo(id, &quot;MyTagId&quot;)
697     *         .setTitle(&quot;My Content Recommendation&quot;)
698     *         .setText(&quot;An example of content recommendation&quot;)
699     *         .setContentImage(myBitmap)
700     *         .setBadgeIcon(R.drawable.app_icon)
701     *         .setGroup(&quot;Trending&quot;)
702     *         .build();
703     * </pre>
704     */
705    public static final class Builder {
706        private String mBuilderIdTag;
707        private String mBuilderTitle;
708        private String mBuilderText;
709        private String mBuilderSourceName;
710        private Bitmap mBuilderContentImage;
711        private int mBuilderBadgeIconId;
712        private String mBuilderBackgroundImageUri;
713        private int mBuilderColor;
714        private String mBuilderGroup;
715        private String mBuilderSortKey;
716        private int mBuilderProgressAmount;
717        private int mBuilderProgressMax;
718        private boolean mBuilderAutoDismiss;
719        private IntentData mBuilderContentIntentData;
720        private IntentData mBuilderDismissIntentData;
721        private String[] mBuilderContentTypes;
722        private String[] mBuilderContentGenres;
723        private String mBuilderPriceType;
724        private String mBuilderPriceValue;
725        private int mBuilderStatus;
726        private String mBuilderMaturityRating;
727        private long mBuilderRunningTime;
728
729        /**
730         * Constructs a new Builder.
731         *
732         */
733        public Builder() {
734        }
735
736        /**
737         * Sets the Id tag that uniquely identifies this recommendation object.
738         *
739         * @param idTag A String tag identifier for this recommendation.
740         * @return The Builder object, for chaining.
741         */
742        public Builder setIdTag(String idTag) {
743            mBuilderIdTag = checkNotNull(idTag);
744            return this;
745        }
746
747        /**
748         * Sets the content title for the recommendation.
749         *
750         * @param title A String containing the recommendation content title.
751         * @return The Builder object, for chaining.
752         */
753        public Builder setTitle(String title) {
754            mBuilderTitle = checkNotNull(title);
755            return this;
756        }
757
758        /**
759         * Sets the description text for the recommendation.
760         *
761         * @param description A String containing the recommendation description text.
762         * @return The Builder object, for chaining.
763         */
764        public Builder setText(@Nullable String description) {
765            mBuilderText = description;
766            return this;
767        }
768
769        /**
770         * Sets the source application name for the recommendation.
771         * <P>
772         * If the source name is never set, or set to null, the application name retrieved from its
773         * package will be used by default.
774         *
775         * @param source A String containing the recommendation source name.
776         * @return The Builder object, for chaining.
777         */
778        public Builder setSourceName(@Nullable String source) {
779            mBuilderSourceName = source;
780            return this;
781        }
782
783        /**
784         * Sets the recommendation image.
785         *
786         * @param image A Bitmap containing the recommendation image.
787         * @return The Builder object, for chaining.
788         */
789        public Builder setContentImage(Bitmap image) {
790            mBuilderContentImage = checkNotNull(image);
791            return this;
792        }
793
794        /**
795         * Sets the resource ID for the recommendation badging icon.
796         * <p>
797         * The resource id represents the icon resource in the source application package. If not
798         * set, or an invalid resource ID is specified, the application icon retrieved from its
799         * package will be used by default.
800         *
801         * @param iconResourceId An integer id for the badge icon resource.
802         * @return The Builder object, for chaining.
803         */
804        public Builder setBadgeIcon(@DrawableRes int iconResourceId) {
805            mBuilderBadgeIconId = iconResourceId;
806            return this;
807        }
808
809        /**
810         * Sets the Content URI that will be used to retrieve the background image for the
811         * recommendation.
812         *
813         * @param imageUri A Content URI pointing to the recommendation background image.
814         * @return The Builder object, for chaining.
815         */
816        public Builder setBackgroundImageUri(@Nullable String imageUri) {
817            mBuilderBackgroundImageUri = imageUri;
818            return this;
819        }
820
821        /**
822         * Sets the accent color value to be used in the UI when displaying this content
823         * recommendation to the user.
824         *
825         * @param color An integer value representing the accent color for this recommendation.
826         * @return The Builder object, for chaining.
827         */
828        public Builder setColor(@ColorInt int color) {
829            mBuilderColor = color;
830            return this;
831        }
832
833        /**
834         * Sets the String group ID tag for the recommendation.
835         * <p>
836         * Recommendations in the same group are ranked by the Home Screen together, and the sort
837         * order within a group is respected. This can be useful if the application has different
838         * sources for recommendations, like "trending", "subscriptions", and "new music" categories
839         * for YouTube, where the user can be more interested in recommendations from one group than
840         * another.
841         *
842         * @param groupTag A String containing the group ID tag for this recommendation.
843         * @return The Builder object, for chaining.
844         */
845        public Builder setGroup(@Nullable String groupTag) {
846            mBuilderGroup = groupTag;
847            return this;
848        }
849
850        /**
851         * Sets the String sort key for the recommendation.
852         * <p>
853         * The sort key must be a String representation of a float number between 0.0 and 1.0, and
854         * is used to indicate the relative importance (and sort order) of a single recommendation
855         * within its specified group. The recommendations will be ordered in decreasing order of
856         * importance within a given group.
857         *
858         * @param sortKey A String containing the sort key for this recommendation.
859         * @return The Builder object, for chaining.
860         */
861        public Builder setSortKey(@Nullable String sortKey) {
862            mBuilderSortKey = sortKey;
863            return this;
864        }
865
866        /**
867         * Sets the progress information for the content pointed to by the recommendation.
868         *
869         * @param max The maximum value for the progress of this content.
870         * @param progress The progress amount for this content. Must be in the range (0 - max).
871         * @return The Builder object, for chaining.
872         */
873        public Builder setProgress(int max, int progress) {
874            if (max < 0 || progress < 0) {
875                throw new IllegalArgumentException();
876            }
877            mBuilderProgressMax = max;
878            mBuilderProgressAmount = progress;
879            return this;
880        }
881
882        /**
883         * Sets the flag indicating if the recommendation should be dismissed automatically.
884         * <p>
885         * Auto-dismiss notifications are automatically removed from the Home Screen when the user
886         * clicks on them.
887         *
888         * @param autoDismiss A boolean indicating if the recommendation should be auto dismissed or
889         *            not.
890         * @return The Builder object, for chaining.
891         */
892        public Builder setAutoDismiss(boolean autoDismiss) {
893            mBuilderAutoDismiss = autoDismiss;
894            return this;
895        }
896
897        /**
898         * Sets the data for the Intent that will be issued when the user clicks on the
899         * recommendation.
900         * <p>
901         * The Intent data fields provided correspond to the fields passed into the
902         * {@link PendingIntent} factory methods, when creating a new PendingIntent. The actual
903         * PengindIntent object will only be created at the time a recommendation is posted to the
904         * Home Screen.
905         *
906         * @param intentType The type of {@link PendingIntent} to be created when posting this
907         *            recommendation.
908         * @param intent The Intent which to be issued when the recommendation is clicked on.
909         * @param requestCode The private request code to be used when creating the
910         *            {@link PendingIntent}
911         * @param options Only used for the Activity Intent type. Additional options for how the
912         *            Activity should be started. May be null if there are no options.
913         * @return The Builder object, for chaining.
914         */
915        public Builder setContentIntentData(@IntentType int intentType, Intent intent,
916                int requestCode, @Nullable Bundle options) {
917            if (intentType != INTENT_TYPE_ACTIVITY &&
918                    intentType != INTENT_TYPE_BROADCAST &&
919                    intentType != INTENT_TYPE_SERVICE) {
920                throw new IllegalArgumentException("Invalid Intent type specified.");
921            }
922
923            mBuilderContentIntentData = new IntentData();
924            mBuilderContentIntentData.mType = intentType;
925            mBuilderContentIntentData.mIntent = checkNotNull(intent);
926            mBuilderContentIntentData.mRequestCode = requestCode;
927            mBuilderContentIntentData.mOptions = options;
928
929            return this;
930        }
931
932        /**
933         * Sets the data for the Intent that will be issued when the recommendation gets dismissed
934         * from the Home Screen, due to an user action.
935         * <p>
936         * The Intent data fields provided correspond to the fields passed into the
937         * {@link PendingIntent} factory methods, when creating a new PendingIntent. The actual
938         * PengindIntent object will only be created at the time a recommendation is posted to the
939         * Home Screen.
940         *
941         * @param intentType The type of {@link PendingIntent} to be created when posting this
942         *            recommendation.
943         * @param intent The Intent which gets issued when the recommendation is dismissed from the
944         *            Home Screen.
945         * @param requestCode The private request code to be used when creating the
946         *            {@link PendingIntent}
947         * @param options Only used for the Activity Intent type. Additional options for how the
948         *            Activity should be started. May be null if there are no options.
949         * @return The Builder object, for chaining.
950         */
951        public Builder setDismissIntentData(@IntentType int intentType, @Nullable Intent intent,
952                int requestCode, @Nullable Bundle options) {
953            if (intent != null) {
954                if (intentType != INTENT_TYPE_ACTIVITY &&
955                        intentType != INTENT_TYPE_BROADCAST &&
956                        intentType != INTENT_TYPE_SERVICE) {
957                    throw new IllegalArgumentException("Invalid Intent type specified.");
958                }
959
960                mBuilderDismissIntentData = new IntentData();
961                mBuilderDismissIntentData.mType = intentType;
962                mBuilderDismissIntentData.mIntent = intent;
963                mBuilderDismissIntentData.mRequestCode = requestCode;
964                mBuilderDismissIntentData.mOptions = options;
965            } else {
966                mBuilderDismissIntentData = null;
967            }
968            return this;
969        }
970
971        /**
972         * Sets the content types associated with the content recommendation. The first tag entry
973         * will be considered the primary type for the content and will be used for ranking
974         * purposes. Other secondary type tags may be provided, if applicable, and may be used for
975         * filtering purposes.
976         *
977         * @param types Array of predefined type tags (see the <code>CONTENT_TYPE_*</code>
978         *            constants) that describe the recommended content.
979         */
980        public Builder setContentTypes(String[] types) {
981            mBuilderContentTypes = checkNotNull(types);
982            return this;
983        }
984
985        /**
986         * Sets the content genres for the recommendation. These genres may be used for content
987         * ranking. Genres are open ended String tags.
988         * <p>
989         * Some examples: "comedy", "action", "dance", "electronica", "racing", etc.
990         *
991         * @param genres Array of genre string tags that describe the recommended content.
992         */
993        public Builder setGenres(String[] genres) {
994            mBuilderContentGenres = genres;
995            return this;
996        }
997
998        /**
999         * Sets the pricing and availability information for the recommendation. The provided
1000         * information will indicate the access model for the content (free, rental, purchase or
1001         * subscription) and the price value (if not free).
1002         *
1003         * @param priceType Pricing type for this content. Must be one of the predefined pricing
1004         *            type tags (see the <code>CONTENT_PRICING_*</code> constants).
1005         * @param priceValue A string containing a representation of the content price in the
1006         *            current locale and currency.
1007         */
1008        public Builder setPricingInformation(@ContentPricing String priceType,
1009                @Nullable String priceValue) {
1010            mBuilderPriceType = checkNotNull(priceType);
1011            mBuilderPriceValue = priceValue;
1012            return this;
1013        }
1014
1015        /**
1016         * Sets the availability status for the content. This status indicates whether the referred
1017         * content is ready to be consumed on the device, or if the user must first purchase, rent,
1018         * subscribe to, or download the content.
1019         *
1020         * @param contentStatus The status value for this content. Must be one of the predefined
1021         *            content status values (see the <code>CONTENT_STATUS_*</code> constants).
1022         */
1023        public Builder setStatus(@ContentStatus int contentStatus) {
1024            mBuilderStatus = contentStatus;
1025            return this;
1026        }
1027
1028        /**
1029         * Sets the maturity level rating for the content.
1030         *
1031         * @param maturityRating A tag indicating the maturity level rating for the content. This
1032         *            tag must be one of the predefined maturity rating tags (see the <code>
1033         *            CONTENT_MATURITY_*</code> constants).
1034         */
1035        public Builder setMaturityRating(@ContentMaturity String maturityRating) {
1036            mBuilderMaturityRating = checkNotNull(maturityRating);
1037            return this;
1038        }
1039
1040        /**
1041         * Sets the running time (when applicable) for the content.
1042         *
1043         * @param length The running time, in seconds, of the content.
1044         */
1045        public Builder setRunningTime(long length) {
1046            if (length < 0) {
1047                throw new IllegalArgumentException();
1048            }
1049            mBuilderRunningTime = length;
1050            return this;
1051        }
1052
1053        /**
1054         * Combine all of the options that have been set and return a new
1055         * {@link ContentRecommendation} object.
1056         */
1057        public ContentRecommendation build() {
1058            return new ContentRecommendation(this);
1059        }
1060    }
1061
1062    /**
1063     * Returns a {@link android.app.Notification Notification} object which contains the content
1064     * recommendation data encapsulated by this object, which can be used for posting the
1065     * recommendation via the {@link android.app.NotificationManager NotificationManager}.
1066     *
1067     * @param context A {@link Context} that will be used to construct the
1068     *            {@link android.app.Notification Notification} object which will carry the
1069     *            recommendation data.
1070     * @return A {@link android.app.Notification Notification} containing the stored recommendation
1071     *         data.
1072     */
1073    public Notification getNotificationObject(Context context) {
1074        Notification.Builder builder = new Notification.Builder(context);
1075        RecommendationExtender recExtender = new RecommendationExtender();
1076
1077        // Encode all the content recommendation data in a Notification object
1078
1079        builder.setCategory(Notification.CATEGORY_RECOMMENDATION);
1080        builder.setContentTitle(mTitle);
1081        builder.setContentText(mText);
1082        builder.setContentInfo(mSourceName);
1083        builder.setLargeIcon(mContentImage);
1084        builder.setSmallIcon(mBadgeIconId);
1085        if (mBackgroundImageUri != null) {
1086            builder.getExtras().putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
1087                    mBackgroundImageUri);
1088        }
1089        builder.setColor(mColor);
1090        builder.setGroup(mGroup);
1091        builder.setSortKey(mSortKey);
1092        builder.setProgress(mProgressMax, mProgressAmount, false);
1093        builder.setAutoCancel(mAutoDismiss);
1094
1095        if (mContentIntentData != null) {
1096            PendingIntent contentPending;
1097            if (mContentIntentData.mType == INTENT_TYPE_ACTIVITY) {
1098                contentPending = PendingIntent.getActivity(context, mContentIntentData.mRequestCode,
1099                        mContentIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT,
1100                        mContentIntentData.mOptions);
1101            } else if (mContentIntentData.mType == INTENT_TYPE_SERVICE) {
1102                contentPending = PendingIntent.getService(context, mContentIntentData.mRequestCode,
1103                        mContentIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT);
1104            } else { // Default:INTENT_TYPE_BROADCAST{
1105                contentPending = PendingIntent.getBroadcast(context,
1106                        mContentIntentData.mRequestCode,
1107                        mContentIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT);
1108            }
1109            builder.setContentIntent(contentPending);
1110        }
1111
1112        if (mDismissIntentData != null) {
1113            PendingIntent dismissPending;
1114            if (mDismissIntentData.mType == INTENT_TYPE_ACTIVITY) {
1115                dismissPending = PendingIntent.getActivity(context, mDismissIntentData.mRequestCode,
1116                        mDismissIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT,
1117                        mDismissIntentData.mOptions);
1118            } else if (mDismissIntentData.mType == INTENT_TYPE_SERVICE) {
1119                dismissPending = PendingIntent.getService(context, mDismissIntentData.mRequestCode,
1120                        mDismissIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT);
1121            } else { // Default:INTENT_TYPE_BROADCAST{
1122                dismissPending = PendingIntent.getBroadcast(context,
1123                        mDismissIntentData.mRequestCode,
1124                        mDismissIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT);
1125            }
1126            builder.setDeleteIntent(dismissPending);
1127        }
1128
1129        recExtender.setContentTypes(mContentTypes);
1130        recExtender.setGenres(mContentGenres);
1131        recExtender.setPricingInformation(mPriceType, mPriceValue);
1132        recExtender.setStatus(mStatus);
1133        recExtender.setMaturityRating(mMaturityRating);
1134        recExtender.setRunningTime(mRunningTime);
1135
1136        builder.extend(recExtender);
1137        Notification notif = builder.build();
1138        return notif;
1139    }
1140
1141    /**
1142     * Ensures that an object reference passed as a parameter to the calling method is not null.
1143     *
1144     * @param reference an object reference
1145     * @return the non-null reference that was validated
1146     * @throws NullPointerException if {@code reference} is null
1147     */
1148    private static <T> T checkNotNull(final T reference) {
1149        if (reference == null) {
1150            throw new NullPointerException();
1151        }
1152        return reference;
1153    }
1154
1155}