1 /*
2  * Copyright (C) 2013 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 
17 package android.support.v4.media;
18 
19 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20 
21 import android.os.Build;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.support.annotation.IntDef;
25 import android.support.annotation.RestrictTo;
26 import android.util.Log;
27 
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 
31 /**
32  * A class to encapsulate rating information used as content metadata.
33  * A rating is defined by its rating style (see {@link #RATING_HEART},
34  * {@link #RATING_THUMB_UP_DOWN}, {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
35  * {@link #RATING_5_STARS} or {@link #RATING_PERCENTAGE}) and the actual rating value (which may
36  * be defined as "unrated"), both of which are defined when the rating instance is constructed
37  * through one of the factory methods.
38  */
39 public final class RatingCompat implements Parcelable {
40     private final static String TAG = "Rating";
41 
42     /**
43      * @hide
44      */
45     @RestrictTo(LIBRARY_GROUP)
46     @IntDef({RATING_NONE, RATING_HEART, RATING_THUMB_UP_DOWN, RATING_3_STARS, RATING_4_STARS,
47             RATING_5_STARS, RATING_PERCENTAGE})
48     @Retention(RetentionPolicy.SOURCE)
49     public @interface Style {}
50 
51     /**
52      * @hide
53      */
54     @RestrictTo(LIBRARY_GROUP)
55     @IntDef({RATING_3_STARS, RATING_4_STARS, RATING_5_STARS})
56     @Retention(RetentionPolicy.SOURCE)
57     public @interface StarStyle {}
58 
59     /**
60      * Indicates a rating style is not supported. A Rating will never have this
61      * type, but can be used by other classes to indicate they do not support
62      * Rating.
63      */
64     public final static int RATING_NONE = 0;
65 
66     /**
67      * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to
68      * indicate the content referred to is a favorite (or not).
69      */
70     public final static int RATING_HEART = 1;
71 
72     /**
73      * A rating style for "thumb up" vs "thumb down".
74      */
75     public final static int RATING_THUMB_UP_DOWN = 2;
76 
77     /**
78      * A rating style with 0 to 3 stars.
79      */
80     public final static int RATING_3_STARS = 3;
81 
82     /**
83      * A rating style with 0 to 4 stars.
84      */
85     public final static int RATING_4_STARS = 4;
86 
87     /**
88      * A rating style with 0 to 5 stars.
89      */
90     public final static int RATING_5_STARS = 5;
91 
92     /**
93      * A rating style expressed as a percentage.
94      */
95     public final static int RATING_PERCENTAGE = 6;
96 
97     private final static float RATING_NOT_RATED = -1.0f;
98 
99     private final int mRatingStyle;
100     private final float mRatingValue;
101 
102     private Object mRatingObj; // framework Rating object
103 
RatingCompat(@tyle int ratingStyle, float rating)104     RatingCompat(@Style int ratingStyle, float rating) {
105         mRatingStyle = ratingStyle;
106         mRatingValue = rating;
107     }
108 
109     @Override
toString()110     public String toString() {
111         return "Rating:style=" + mRatingStyle + " rating="
112                 + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
113     }
114 
115     @Override
describeContents()116     public int describeContents() {
117         return mRatingStyle;
118     }
119 
120     @Override
writeToParcel(Parcel dest, int flags)121     public void writeToParcel(Parcel dest, int flags) {
122         dest.writeInt(mRatingStyle);
123         dest.writeFloat(mRatingValue);
124     }
125 
126     public static final Parcelable.Creator<RatingCompat> CREATOR
127             = new Parcelable.Creator<RatingCompat>() {
128         /**
129          * Rebuilds a Rating previously stored with writeToParcel().
130          * @param p    Parcel object to read the Rating from
131          * @return a new Rating created from the data in the parcel
132          */
133         @Override
134         public RatingCompat createFromParcel(Parcel p) {
135             return new RatingCompat(p.readInt(), p.readFloat());
136         }
137 
138         @Override
139         public RatingCompat[] newArray(int size) {
140             return new RatingCompat[size];
141         }
142     };
143 
144     /**
145      * Return a Rating instance with no rating.
146      * Create and return a new Rating instance with no rating known for the given
147      * rating style.
148      * @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
149      *    {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
150      *    or {@link #RATING_PERCENTAGE}.
151      * @return null if an invalid rating style is passed, a new Rating instance otherwise.
152      */
newUnratedRating(@tyle int ratingStyle)153     public static RatingCompat newUnratedRating(@Style int ratingStyle) {
154         switch(ratingStyle) {
155             case RATING_HEART:
156             case RATING_THUMB_UP_DOWN:
157             case RATING_3_STARS:
158             case RATING_4_STARS:
159             case RATING_5_STARS:
160             case RATING_PERCENTAGE:
161                 return new RatingCompat(ratingStyle, RATING_NOT_RATED);
162             default:
163                 return null;
164         }
165     }
166 
167     /**
168      * Return a Rating instance with a heart-based rating.
169      * Create and return a new Rating instance with a rating style of {@link #RATING_HEART},
170      * and a heart-based rating.
171      * @param hasHeart true for a "heart selected" rating, false for "heart unselected".
172      * @return a new Rating instance.
173      */
newHeartRating(boolean hasHeart)174     public static RatingCompat newHeartRating(boolean hasHeart) {
175         return new RatingCompat(RATING_HEART, hasHeart ? 1.0f : 0.0f);
176     }
177 
178     /**
179      * Return a Rating instance with a thumb-based rating.
180      * Create and return a new Rating instance with a {@link #RATING_THUMB_UP_DOWN}
181      * rating style, and a "thumb up" or "thumb down" rating.
182      * @param thumbIsUp true for a "thumb up" rating, false for "thumb down".
183      * @return a new Rating instance.
184      */
newThumbRating(boolean thumbIsUp)185     public static RatingCompat newThumbRating(boolean thumbIsUp) {
186         return new RatingCompat(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f);
187     }
188 
189     /**
190      * Return a Rating instance with a star-based rating.
191      * Create and return a new Rating instance with one of the star-base rating styles
192      * and the given integer or fractional number of stars. Non integer values can for instance
193      * be used to represent an average rating value, which might not be an integer number of stars.
194      * @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
195      *     {@link #RATING_5_STARS}.
196      * @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to
197      *     the rating style.
198      * @return null if the rating style is invalid, or the rating is out of range,
199      *     a new Rating instance otherwise.
200      */
newStarRating(@tarStyle int starRatingStyle, float starRating)201     public static RatingCompat newStarRating(@StarStyle int starRatingStyle,
202             float starRating) {
203         float maxRating = -1.0f;
204         switch(starRatingStyle) {
205             case RATING_3_STARS:
206                 maxRating = 3.0f;
207                 break;
208             case RATING_4_STARS:
209                 maxRating = 4.0f;
210                 break;
211             case RATING_5_STARS:
212                 maxRating = 5.0f;
213                 break;
214             default:
215                 Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating");
216                         return null;
217         }
218         if ((starRating < 0.0f) || (starRating > maxRating)) {
219             Log.e(TAG, "Trying to set out of range star-based rating");
220             return null;
221         }
222         return new RatingCompat(starRatingStyle, starRating);
223     }
224 
225     /**
226      * Return a Rating instance with a percentage-based rating.
227      * Create and return a new Rating instance with a {@link #RATING_PERCENTAGE}
228      * rating style, and a rating of the given percentage.
229      * @param percent the value of the rating
230      * @return null if the rating is out of range, a new Rating instance otherwise.
231      */
newPercentageRating(float percent)232     public static RatingCompat newPercentageRating(float percent) {
233         if ((percent < 0.0f) || (percent > 100.0f)) {
234             Log.e(TAG, "Invalid percentage-based rating value");
235             return null;
236         } else {
237             return new RatingCompat(RATING_PERCENTAGE, percent);
238         }
239     }
240 
241     /**
242      * Return whether there is a rating value available.
243      * @return true if the instance was not created with {@link #newUnratedRating(int)}.
244      */
isRated()245     public boolean isRated() {
246         return mRatingValue >= 0.0f;
247     }
248 
249     /**
250      * Return the rating style.
251      * @return one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
252      *    {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
253      *    or {@link #RATING_PERCENTAGE}.
254      */
255     @Style
getRatingStyle()256     public int getRatingStyle() {
257         return mRatingStyle;
258     }
259 
260     /**
261      * Return whether the rating is "heart selected".
262      * @return true if the rating is "heart selected", false if the rating is "heart unselected",
263      *    if the rating style is not {@link #RATING_HEART} or if it is unrated.
264      */
hasHeart()265     public boolean hasHeart() {
266         if (mRatingStyle != RATING_HEART) {
267             return false;
268         } else {
269             return (mRatingValue == 1.0f);
270         }
271     }
272 
273     /**
274      * Return whether the rating is "thumb up".
275      * @return true if the rating is "thumb up", false if the rating is "thumb down",
276      *    if the rating style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated.
277      */
isThumbUp()278     public boolean isThumbUp() {
279         if (mRatingStyle != RATING_THUMB_UP_DOWN) {
280             return false;
281         } else {
282             return (mRatingValue == 1.0f);
283         }
284     }
285 
286     /**
287      * Return the star-based rating value.
288      * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
289      *    not star-based, or if it is unrated.
290      */
getStarRating()291     public float getStarRating() {
292         switch (mRatingStyle) {
293             case RATING_3_STARS:
294             case RATING_4_STARS:
295             case RATING_5_STARS:
296                 if (isRated()) {
297                     return mRatingValue;
298                 }
299                 // fall through
300             default:
301                 return -1.0f;
302         }
303     }
304 
305     /**
306      * Return the percentage-based rating value.
307      * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
308      *    not percentage-based, or if it is unrated.
309      */
getPercentRating()310     public float getPercentRating() {
311         if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) {
312             return -1.0f;
313         } else {
314             return mRatingValue;
315         }
316     }
317 
318     /**
319      * Creates an instance from a framework {@link android.media.Rating} object.
320      * <p>
321      * This method is only supported on API 19+.
322      * </p>
323      *
324      * @param ratingObj A {@link android.media.Rating} object, or null if none.
325      * @return An equivalent {@link RatingCompat} object, or null if none.
326      */
fromRating(Object ratingObj)327     public static RatingCompat fromRating(Object ratingObj) {
328         if (ratingObj != null && Build.VERSION.SDK_INT >= 19) {
329             final int ratingStyle = RatingCompatKitkat.getRatingStyle(ratingObj);
330             final RatingCompat rating;
331             if (RatingCompatKitkat.isRated(ratingObj)) {
332                 switch (ratingStyle) {
333                     case RATING_HEART:
334                         rating = newHeartRating(RatingCompatKitkat.hasHeart(ratingObj));
335                         break;
336                     case RATING_THUMB_UP_DOWN:
337                         rating = newThumbRating(RatingCompatKitkat.isThumbUp(ratingObj));
338                         break;
339                     case RATING_3_STARS:
340                     case RATING_4_STARS:
341                     case RATING_5_STARS:
342                         rating = newStarRating(ratingStyle,
343                                 RatingCompatKitkat.getStarRating(ratingObj));
344                         break;
345                     case RATING_PERCENTAGE:
346                         rating = newPercentageRating(
347                                 RatingCompatKitkat.getPercentRating(ratingObj));
348                         break;
349                     default:
350                         return null;
351                 }
352             } else {
353                 rating = newUnratedRating(ratingStyle);
354             }
355             rating.mRatingObj = ratingObj;
356             return rating;
357         } else {
358             return null;
359         }
360     }
361 
362     /**
363      * Gets the underlying framework {@link android.media.Rating} object.
364      * <p>
365      * This method is only supported on API 19+.
366      * </p>
367      *
368      * @return An equivalent {@link android.media.Rating} object, or null if none.
369      */
getRating()370     public Object getRating() {
371         if (mRatingObj == null && Build.VERSION.SDK_INT >= 19) {
372             if (isRated()) {
373                 switch (mRatingStyle) {
374                     case RATING_HEART:
375                         mRatingObj = RatingCompatKitkat.newHeartRating(hasHeart());
376                         break;
377                     case RATING_THUMB_UP_DOWN:
378                         mRatingObj = RatingCompatKitkat.newThumbRating(isThumbUp());
379                         break;
380                     case RATING_3_STARS:
381                     case RATING_4_STARS:
382                     case RATING_5_STARS:
383                         mRatingObj = RatingCompatKitkat.newStarRating(mRatingStyle,
384                                 getStarRating());
385                         break;
386                     case RATING_PERCENTAGE:
387                         mRatingObj = RatingCompatKitkat.newPercentageRating(getPercentRating());
388                         break;
389                     default:
390                         return null;
391                 }
392             } else {
393                 mRatingObj = RatingCompatKitkat.newUnratedRating(mRatingStyle);
394             }
395         }
396         return mRatingObj;
397     }
398 }
399