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 167 @TrickplaySetting getTrickplaySetting(Context context)168 public static synchronized int getTrickplaySetting(Context context) { 169 SoftPreconditions.checkState(sInitialized); 170 if (useContentProvider(context)) { 171 return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); 172 } else { 173 return getSharedPreferences(context) 174 .getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); 175 } 176 } 177 setTrickplaySetting( Context context, @TrickplaySetting int trickplaySetting)178 public static synchronized void setTrickplaySetting( 179 Context context, @TrickplaySetting int trickplaySetting) { 180 SoftPreconditions.checkState(sInitialized); 181 SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET); 182 if (useContentProvider(context)) { 183 setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting); 184 } else { 185 getSharedPreferences(context) 186 .edit() 187 .putInt(PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting) 188 .apply(); 189 } 190 } 191 getStoreTsStream(Context context)192 public static synchronized boolean getStoreTsStream(Context context) { 193 SoftPreconditions.checkState(sInitialized); 194 if (useContentProvider(context)) { 195 return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false); 196 } else { 197 return getSharedPreferences(context).getBoolean(PREFS_KEY_STORE_TS_STREAM, false); 198 } 199 } 200 setStoreTsStream(Context context, boolean shouldStore)201 public static synchronized void setStoreTsStream(Context context, boolean shouldStore) { 202 if (useContentProvider(context)) { 203 setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore); 204 } else { 205 getSharedPreferences(context) 206 .edit() 207 .putBoolean(PREFS_KEY_STORE_TS_STREAM, shouldStore) 208 .apply(); 209 } 210 } 211 getLastPostalCode(Context context)212 public static synchronized String getLastPostalCode(Context context) { 213 SoftPreconditions.checkState(sInitialized); 214 if (useContentProvider(context)) { 215 return sPreferenceValues.getString(PREFS_KEY_LAST_POSTAL_CODE); 216 } else { 217 return getSharedPreferences(context).getString(PREFS_KEY_LAST_POSTAL_CODE, null); 218 } 219 } 220 setLastPostalCode(Context context, String postalCode)221 public static synchronized void setLastPostalCode(Context context, String postalCode) { 222 if (useContentProvider(context)) { 223 setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode); 224 } else { 225 getSharedPreferences(context) 226 .edit() 227 .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode) 228 .apply(); 229 } 230 } 231 getSharedPreferences(Context context)232 protected static SharedPreferences getSharedPreferences(Context context) { 233 return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE); 234 } 235 setPreference(Context context, String key, String value)236 private static synchronized void setPreference(Context context, String key, String value) { 237 sPreferenceValues.putString(key, value); 238 savePreference(context, key, value); 239 } 240 setPreference(Context context, String key, int value)241 private static synchronized void setPreference(Context context, String key, int value) { 242 sPreferenceValues.putInt(key, value); 243 savePreference(context, key, Integer.toString(value)); 244 } 245 setPreference(Context context, String key, long value)246 private static synchronized void setPreference(Context context, String key, long value) { 247 sPreferenceValues.putLong(key, value); 248 savePreference(context, key, Long.toString(value)); 249 } 250 setPreference(Context context, String key, boolean value)251 private static synchronized void setPreference(Context context, String key, boolean value) { 252 sPreferenceValues.putBoolean(key, value); 253 savePreference(context, key, Boolean.toString(value)); 254 } 255 savePreference( final Context context, final String key, final String value)256 private static void savePreference( 257 final Context context, final String key, final String value) { 258 new AsyncTask<Void, Void, Void>() { 259 @Override 260 protected Void doInBackground(Void... params) { 261 ContentResolver resolver = context.getContentResolver(); 262 ContentValues values = new ContentValues(); 263 values.put(Preferences.COLUMN_KEY, key); 264 values.put(Preferences.COLUMN_VALUE, value); 265 try { 266 resolver.insert(Preferences.CONTENT_URI, values); 267 } catch (Exception e) { 268 SoftPreconditions.warn( 269 TAG, "setPreference", e, "Error writing preference values"); 270 } 271 return null; 272 } 273 }.execute(); 274 } 275 276 private static class LoadPreferencesTask extends AsyncTask<Void, Void, Bundle> { 277 private final Context mContext; 278 LoadPreferencesTask(Context context)279 private LoadPreferencesTask(Context context) { 280 mContext = context; 281 } 282 283 @Override doInBackground(Void... params)284 protected Bundle doInBackground(Void... params) { 285 Bundle bundle = new Bundle(); 286 ContentResolver resolver = mContext.getContentResolver(); 287 String[] projection = new String[] {Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE}; 288 try (Cursor cursor = 289 resolver.query(Preferences.CONTENT_URI, projection, null, null, null)) { 290 if (cursor != null) { 291 while (!isCancelled() && cursor.moveToNext()) { 292 String key = cursor.getString(0); 293 String value = cursor.getString(1); 294 Class prefClass = sPref2TypeMapping.get(key); 295 if (prefClass == int.class) { 296 try { 297 bundle.putInt(key, Integer.parseInt(value)); 298 } catch (NumberFormatException e) { 299 Log.w(TAG, "Invalid format, key=" + key + ", value=" + value); 300 } 301 } else if (prefClass == long.class) { 302 try { 303 bundle.putLong(key, Long.parseLong(value)); 304 } catch (NumberFormatException e) { 305 Log.w(TAG, "Invalid format, key=" + key + ", value=" + value); 306 } 307 } else if (prefClass == boolean.class) { 308 bundle.putBoolean(key, Boolean.parseBoolean(value)); 309 } else { 310 bundle.putString(key, value); 311 } 312 } 313 } 314 } catch (Exception e) { 315 SoftPreconditions.warn(TAG, "getPreference", e, "Error querying preference values"); 316 return null; 317 } 318 return bundle; 319 } 320 321 @Override onPostExecute(Bundle bundle)322 protected void onPostExecute(Bundle bundle) { 323 synchronized (CommonPreferences.class) { 324 if (bundle != null) { 325 sPreferenceValues.putAll(bundle); 326 } 327 } 328 if (sPreferencesChangedListener != null) { 329 sPreferencesChangedListener.onCommonPreferencesChanged(); 330 } 331 } 332 } 333 } 334