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  */
16 package com.android.tv.common;
17 
18 import android.content.ContentResolver;
19 import android.content.ContentValues;
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.database.ContentObserver;
23 import android.database.Cursor;
24 import android.os.AsyncTask;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.support.annotation.GuardedBy;
28 import android.support.annotation.IntDef;
29 import android.support.annotation.MainThread;
30 import android.util.Log;
31 import com.android.tv.common.CommonPreferenceProvider.Preferences;
32 import com.android.tv.common.util.CommonUtils;
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.util.HashMap;
36 import java.util.Map;
37 
38 /** A helper class for setting/getting common preferences across applications. */
39 public class CommonPreferences {
40     private static final String TAG = "CommonPreferences";
41 
42     private static final String PREFS_KEY_LAUNCH_SETUP = "launch_setup";
43     private static final String PREFS_KEY_STORE_TS_STREAM = "store_ts_stream";
44     private static final String PREFS_KEY_TRICKPLAY_SETTING = "trickplay_setting";
45     private static final String PREFS_KEY_LAST_POSTAL_CODE = "last_postal_code";
46 
47     private static final Map<String, Class> sPref2TypeMapping = new HashMap<>();
48 
49     static {
sPref2TypeMapping.put(PREFS_KEY_TRICKPLAY_SETTING, int.class)50         sPref2TypeMapping.put(PREFS_KEY_TRICKPLAY_SETTING, int.class);
sPref2TypeMapping.put(PREFS_KEY_STORE_TS_STREAM, boolean.class)51         sPref2TypeMapping.put(PREFS_KEY_STORE_TS_STREAM, boolean.class);
sPref2TypeMapping.put(PREFS_KEY_LAUNCH_SETUP, boolean.class)52         sPref2TypeMapping.put(PREFS_KEY_LAUNCH_SETUP, boolean.class);
sPref2TypeMapping.put(PREFS_KEY_LAST_POSTAL_CODE, String.class)53         sPref2TypeMapping.put(PREFS_KEY_LAST_POSTAL_CODE, String.class);
54     }
55 
56     private static final String SHARED_PREFS_NAME =
57             CommonConstants.BASE_PACKAGE + ".common.preferences";
58 
59     @IntDef({TRICKPLAY_SETTING_NOT_SET, TRICKPLAY_SETTING_DISABLED, TRICKPLAY_SETTING_ENABLED})
60     @Retention(RetentionPolicy.SOURCE)
61     public @interface TrickplaySetting {}
62 
63     /** Trickplay setting is not changed by a user. Trickplay will be enabled in this case. */
64     public static final int TRICKPLAY_SETTING_NOT_SET = -1;
65 
66     /** Trickplay setting is disabled. */
67     public static final int TRICKPLAY_SETTING_DISABLED = 0;
68 
69     /** Trickplay setting is enabled. */
70     public static final int TRICKPLAY_SETTING_ENABLED = 1;
71 
72     @GuardedBy("CommonPreferences.class")
73     private static final Bundle sPreferenceValues = new Bundle();
74 
75     private static LoadPreferencesTask sLoadPreferencesTask;
76     private static ContentObserver sContentObserver;
77     private static CommonPreferencesChangedListener sPreferencesChangedListener = null;
78 
79     protected static boolean sInitialized;
80 
81     /** Listeners for CommonPreferences change. */
82     public interface CommonPreferencesChangedListener {
onCommonPreferencesChanged()83         void onCommonPreferencesChanged();
84     }
85 
86     /** Initializes the common preferences. */
87     @MainThread
initialize(final Context context)88     public static void initialize(final Context context) {
89         if (sInitialized) {
90             return;
91         }
92         sInitialized = true;
93         if (useContentProvider(context)) {
94             loadPreferences(context);
95             sContentObserver =
96                     new ContentObserver(new Handler()) {
97                         @Override
98                         public void onChange(boolean selfChange) {
99                             loadPreferences(context);
100                         }
101                     };
102             context.getContentResolver()
103                     .registerContentObserver(Preferences.CONTENT_URI, true, sContentObserver);
104         } else {
105             new AsyncTask<Void, Void, Void>() {
106                 @Override
107                 protected Void doInBackground(Void... params) {
108                     getSharedPreferences(context);
109                     return null;
110                 }
111             }.execute();
112         }
113     }
114 
115     /** Releases the resources. */
release(Context context)116     public static synchronized void release(Context context) {
117         if (useContentProvider(context) && sContentObserver != null) {
118             context.getContentResolver().unregisterContentObserver(sContentObserver);
119         }
120         setCommonPreferencesChangedListener(null);
121     }
122 
123     /** Sets the listener for CommonPreferences change. */
setCommonPreferencesChangedListener( CommonPreferencesChangedListener listener)124     public static void setCommonPreferencesChangedListener(
125             CommonPreferencesChangedListener listener) {
126         sPreferencesChangedListener = listener;
127     }
128 
129     /**
130      * Loads the preferences from database.
131      *
132      * <p>This preferences is used across processes, so the preferences should be loaded again when
133      * the databases changes.
134      */
135     @MainThread
loadPreferences(Context context)136     public static void loadPreferences(Context context) {
137         if (sLoadPreferencesTask != null
138                 && sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) {
139             sLoadPreferencesTask.cancel(true);
140         }
141         sLoadPreferencesTask = new LoadPreferencesTask(context);
142         sLoadPreferencesTask.execute();
143     }
144 
useContentProvider(Context context)145     private static boolean useContentProvider(Context context) {
146         // If TIS is a part of LC, it should use ContentProvider to resolve multiple process access.
147         return CommonUtils.isPackagedWithLiveChannels(context);
148     }
149 
shouldShowSetupActivity(Context context)150     public static synchronized boolean shouldShowSetupActivity(Context context) {
151         SoftPreconditions.checkState(sInitialized);
152         if (useContentProvider(context)) {
153             return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP);
154         } else {
155             return getSharedPreferences(context).getBoolean(PREFS_KEY_LAUNCH_SETUP, false);
156         }
157     }
158 
setShouldShowSetupActivity(Context context, boolean need)159     public static synchronized void setShouldShowSetupActivity(Context context, boolean need) {
160         if (useContentProvider(context)) {
161             setPreference(context, PREFS_KEY_LAUNCH_SETUP, need);
162         } else {
163             getSharedPreferences(context).edit().putBoolean(PREFS_KEY_LAUNCH_SETUP, need).apply();
164         }
165     }
166 
getTrickplaySetting(Context context)167     public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) {
168         SoftPreconditions.checkState(sInitialized);
169         if (useContentProvider(context)) {
170             return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
171         } else {
172             return getSharedPreferences(context)
173                     .getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
174         }
175     }
176 
setTrickplaySetting( Context context, @TrickplaySetting int trickplaySetting)177     public static synchronized void setTrickplaySetting(
178             Context context, @TrickplaySetting int trickplaySetting) {
179         SoftPreconditions.checkState(sInitialized);
180         SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET);
181         if (useContentProvider(context)) {
182             setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting);
183         } else {
184             getSharedPreferences(context)
185                     .edit()
186                     .putInt(PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting)
187                     .apply();
188         }
189     }
190 
getStoreTsStream(Context context)191     public static synchronized boolean getStoreTsStream(Context context) {
192         SoftPreconditions.checkState(sInitialized);
193         if (useContentProvider(context)) {
194             return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false);
195         } else {
196             return getSharedPreferences(context).getBoolean(PREFS_KEY_STORE_TS_STREAM, false);
197         }
198     }
199 
setStoreTsStream(Context context, boolean shouldStore)200     public static synchronized void setStoreTsStream(Context context, boolean shouldStore) {
201         if (useContentProvider(context)) {
202             setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore);
203         } else {
204             getSharedPreferences(context)
205                     .edit()
206                     .putBoolean(PREFS_KEY_STORE_TS_STREAM, shouldStore)
207                     .apply();
208         }
209     }
210 
getLastPostalCode(Context context)211     public static synchronized String getLastPostalCode(Context context) {
212         SoftPreconditions.checkState(sInitialized);
213         if (useContentProvider(context)) {
214             return sPreferenceValues.getString(PREFS_KEY_LAST_POSTAL_CODE);
215         } else {
216             return getSharedPreferences(context).getString(PREFS_KEY_LAST_POSTAL_CODE, null);
217         }
218     }
219 
setLastPostalCode(Context context, String postalCode)220     public static synchronized void setLastPostalCode(Context context, String postalCode) {
221         if (useContentProvider(context)) {
222             setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode);
223         } else {
224             getSharedPreferences(context)
225                     .edit()
226                     .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode)
227                     .apply();
228         }
229     }
230 
getSharedPreferences(Context context)231     protected static SharedPreferences getSharedPreferences(Context context) {
232         return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
233     }
234 
setPreference(Context context, String key, String value)235     private static synchronized void setPreference(Context context, String key, String value) {
236         sPreferenceValues.putString(key, value);
237         savePreference(context, key, value);
238     }
239 
setPreference(Context context, String key, int value)240     private static synchronized void setPreference(Context context, String key, int value) {
241         sPreferenceValues.putInt(key, value);
242         savePreference(context, key, Integer.toString(value));
243     }
244 
setPreference(Context context, String key, long value)245     private static synchronized void setPreference(Context context, String key, long value) {
246         sPreferenceValues.putLong(key, value);
247         savePreference(context, key, Long.toString(value));
248     }
249 
setPreference(Context context, String key, boolean value)250     private static synchronized void setPreference(Context context, String key, boolean value) {
251         sPreferenceValues.putBoolean(key, value);
252         savePreference(context, key, Boolean.toString(value));
253     }
254 
savePreference( final Context context, final String key, final String value)255     private static void savePreference(
256             final Context context, final String key, final String value) {
257         new AsyncTask<Void, Void, Void>() {
258             @Override
259             protected Void doInBackground(Void... params) {
260                 ContentResolver resolver = context.getContentResolver();
261                 ContentValues values = new ContentValues();
262                 values.put(Preferences.COLUMN_KEY, key);
263                 values.put(Preferences.COLUMN_VALUE, value);
264                 try {
265                     resolver.insert(Preferences.CONTENT_URI, values);
266                 } catch (Exception e) {
267                     SoftPreconditions.warn(
268                             TAG, "setPreference", e, "Error writing preference values");
269                 }
270                 return null;
271             }
272         }.execute();
273     }
274 
275     private static class LoadPreferencesTask extends AsyncTask<Void, Void, Bundle> {
276         private final Context mContext;
277 
LoadPreferencesTask(Context context)278         private LoadPreferencesTask(Context context) {
279             mContext = context;
280         }
281 
282         @Override
doInBackground(Void... params)283         protected Bundle doInBackground(Void... params) {
284             Bundle bundle = new Bundle();
285             ContentResolver resolver = mContext.getContentResolver();
286             String[] projection = new String[] {Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE};
287             try (Cursor cursor =
288                     resolver.query(Preferences.CONTENT_URI, projection, null, null, null)) {
289                 if (cursor != null) {
290                     while (!isCancelled() && cursor.moveToNext()) {
291                         String key = cursor.getString(0);
292                         String value = cursor.getString(1);
293                         Class prefClass = sPref2TypeMapping.get(key);
294                         if (prefClass == int.class) {
295                             try {
296                                 bundle.putInt(key, Integer.parseInt(value));
297                             } catch (NumberFormatException e) {
298                                 Log.w(TAG, "Invalid format, key=" + key + ", value=" + value);
299                             }
300                         } else if (prefClass == long.class) {
301                             try {
302                                 bundle.putLong(key, Long.parseLong(value));
303                             } catch (NumberFormatException e) {
304                                 Log.w(TAG, "Invalid format, key=" + key + ", value=" + value);
305                             }
306                         } else if (prefClass == boolean.class) {
307                             bundle.putBoolean(key, Boolean.parseBoolean(value));
308                         } else {
309                             bundle.putString(key, value);
310                         }
311                     }
312                 }
313             } catch (Exception e) {
314                 SoftPreconditions.warn(TAG, "getPreference", e, "Error querying preference values");
315                 return null;
316             }
317             return bundle;
318         }
319 
320         @Override
onPostExecute(Bundle bundle)321         protected void onPostExecute(Bundle bundle) {
322             synchronized (CommonPreferences.class) {
323                 if (bundle != null) {
324                     sPreferenceValues.putAll(bundle);
325                 }
326             }
327             if (sPreferencesChangedListener != null) {
328                 sPreferencesChangedListener.onCommonPreferencesChanged();
329             }
330         }
331     }
332 }
333