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