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