1 /*
2  * Copyright (c) 2017 Google Inc.
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 package com.example.android.tv.channelsprograms.util;
17 
18 import android.content.Context;
19 import android.content.SharedPreferences;
20 import android.util.Log;
21 
22 import com.example.android.tv.channelsprograms.model.Movie;
23 import com.example.android.tv.channelsprograms.model.Subscription;
24 import com.google.gson.Gson;
25 import com.google.gson.JsonSyntaxException;
26 
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashSet;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 import java.util.Set;
33 
34 /**
35  * Helper class to store {@link Subscription}s and {@link Movie}s in {@link SharedPreferences}.
36  *
37  * <p>SharedPreferencesHelper provides static methods to set and get these objects.
38  *
39  * <p>The methods of this class should not be called on the UI thread. Marshalling an object into
40  * JSON can be expensive for large objects.
41  */
42 public final class SharedPreferencesHelper {
43 
44     private static final String TAG = "SharedPreferencesHelper";
45 
46     private static final String PREFS_NAME = "com.example.android.tv.recommendations";
47     private static final String PREFS_SUBSCRIPTIONS_KEY =
48             "com.example.android.tv.recommendations.prefs.SUBSCRIPTIONS";
49     private static final String PREFS_SUBSCRIBED_MOVIES_PREFIX =
50             "com.example.android.tv.recommendations.prefs.SUBSCRIBED_MOVIES_";
51 
52     private static final Gson mGson = new Gson();
53 
54     /**
55      * Reads the {@link List <Subscription>} from {@link SharedPreferences}.
56      *
57      * @param context used for getting an instance of shared preferences.
58      * @return a list of subscriptions or an empty list if none exist.
59      */
readSubscriptions(Context context)60     public static List<Subscription> readSubscriptions(Context context) {
61         return getList(context, Subscription.class, PREFS_SUBSCRIPTIONS_KEY);
62     }
63 
64     /**
65      * Overrides the subscriptions stored in {@link SharedPreferences}.
66      *
67      * @param context used for getting an instance of shared preferences.
68      * @param subscriptions to be stored in shared preferences.
69      */
storeSubscriptions(Context context, List<Subscription> subscriptions)70     public static void storeSubscriptions(Context context, List<Subscription> subscriptions) {
71         setList(context, subscriptions, PREFS_SUBSCRIPTIONS_KEY);
72     }
73 
74     /**
75      * Reads the {@link List <Movie>} from {@link SharedPreferences} for a given channel.
76      *
77      * @param context used for getting an instance of shared preferences.
78      * @param channelId of the channel that the movies are associated with.
79      * @return a list of movies or an empty list if none exist.
80      */
readMovies(Context context, long channelId)81     public static List<Movie> readMovies(Context context, long channelId) {
82         return getList(context, Movie.class, PREFS_SUBSCRIBED_MOVIES_PREFIX + channelId);
83     }
84 
85     /**
86      * Overrides the movies stored in {@link SharedPreferences} for the associated channel id.
87      *
88      * @param context used for getting an instance of shared preferences.
89      * @param channelId of the channel that the movies are associated with.
90      * @param movies to be stored.
91      */
storeMovies(Context context, long channelId, List<Movie> movies)92     public static void storeMovies(Context context, long channelId, List<Movie> movies) {
93         setList(context, movies, PREFS_SUBSCRIBED_MOVIES_PREFIX + channelId);
94     }
95 
96     /**
97      * Retrieves a set of Strings from {@link SharedPreferences} and returns as a List.
98      *
99      * @param context used for getting an instance of shared preferences.
100      * @param clazz the class that the strings will be unmarshalled into.
101      * @param key the key in shared preferences to access the string set.
102      * @param <T> the type of object that will be in the returned list, should be the same as the
103      *     clazz that was supplied.
104      * @return a list of <T> objects that were stored in shared preferences or an empty list if no
105      *     objects exists.
106      */
getList(Context context, Class<T> clazz, String key)107     private static <T> List<T> getList(Context context, Class<T> clazz, String key) {
108         SharedPreferences sharedPreferences =
109                 context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
110         Set<String> stringSet = sharedPreferences.getStringSet(key, new HashSet<String>());
111         if (stringSet.isEmpty()) {
112             // Favoring mutability of the list over Collections.emptyList().
113             return new ArrayList<>();
114         }
115         List<T> list = new ArrayList<>(stringSet.size());
116         try {
117             for (String contactString : stringSet) {
118                 list.add(mGson.fromJson(contactString, clazz));
119             }
120         } catch (JsonSyntaxException e) {
121             Log.e(TAG, "Could not parse json.", e);
122             return Collections.emptyList();
123         }
124         return list;
125     }
126 
127     /**
128      * Saves a list of Strings into {@link SharedPreferences}.
129      *
130      * @param context used for getting an instance of shared preferences.
131      * @param list of <T> object that need to be persisted.
132      * @param key the key in shared preferences which the string set will be stored.
133      * @param <T> type the of object we will be marshalling and persisting.
134      */
setList(Context context, List<T> list, String key)135     private static <T> void setList(Context context, List<T> list, String key) {
136         SharedPreferences sharedPreferences =
137                 context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
138         SharedPreferences.Editor editor = sharedPreferences.edit();
139 
140         Set<String> strings = new LinkedHashSet<>(list.size());
141         for (T item : list) {
142             strings.add(mGson.toJson(item));
143         }
144         editor.putStringSet(key, strings);
145         editor.apply();
146     }
147 }
148