1 /* 2 * Copyright (C) 2015 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 com.android.tv.tuner; 18 19 import android.content.ContentResolver; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.content.SharedPreferences; 23 import android.database.ContentObserver; 24 import android.database.Cursor; 25 import android.os.AsyncTask; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.support.annotation.GuardedBy; 29 import android.support.annotation.IntDef; 30 import android.support.annotation.MainThread; 31 32 import com.android.tv.common.SoftPreconditions; 33 import com.android.tv.tuner.TunerPreferenceProvider.Preferences; 34 import com.android.tv.tuner.util.TisConfiguration; 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 38 /** 39 * A helper class for the USB tuner preferences. 40 */ 41 public class TunerPreferences { 42 private static final String TAG = "TunerPreferences"; 43 44 private static final String PREFS_KEY_CHANNEL_DATA_VERSION = "channel_data_version"; 45 private static final String PREFS_KEY_SCANNED_CHANNEL_COUNT = "scanned_channel_count"; 46 private static final String PREFS_KEY_LAST_POSTAL_CODE = "last_postal_code"; 47 private static final String PREFS_KEY_SCAN_DONE = "scan_done"; 48 private static final String PREFS_KEY_LAUNCH_SETUP = "launch_setup"; 49 private static final String PREFS_KEY_STORE_TS_STREAM = "store_ts_stream"; 50 private static final String PREFS_KEY_TRICKPLAY_SETTING = "trickplay_setting"; 51 private static final String PREFS_KEY_TRICKPLAY_EXPIRED_MS = "trickplay_expired_ms"; 52 53 private static final String SHARED_PREFS_NAME = "com.android.tv.tuner.preferences"; 54 55 public static final int CHANNEL_DATA_VERSION_NOT_SET = -1; 56 57 @IntDef({TRICKPLAY_SETTING_NOT_SET, TRICKPLAY_SETTING_DISABLED, TRICKPLAY_SETTING_ENABLED}) 58 @Retention(RetentionPolicy.SOURCE) 59 public @interface TrickplaySetting { 60 } 61 62 /** 63 * Trickplay setting is not changed by a user. Trickplay will be enabled in this case. 64 */ 65 public static final int TRICKPLAY_SETTING_NOT_SET = -1; 66 67 /** 68 * Trickplay setting is disabled. 69 */ 70 public static final int TRICKPLAY_SETTING_DISABLED = 0; 71 72 /** 73 * Trickplay setting is enabled. 74 */ 75 public static final int TRICKPLAY_SETTING_ENABLED = 1; 76 77 @GuardedBy("TunerPreferences.class") 78 private static final Bundle sPreferenceValues = new Bundle(); 79 private static LoadPreferencesTask sLoadPreferencesTask; 80 private static ContentObserver sContentObserver; 81 private static TunerPreferencesChangedListener sPreferencesChangedListener = null; 82 83 private static boolean sInitialized; 84 85 /** 86 * Listeners for TunerPreferences change. 87 */ 88 public interface TunerPreferencesChangedListener { onTunerPreferencesChanged()89 void onTunerPreferencesChanged(); 90 } 91 92 /** 93 * Initializes the USB tuner preferences. 94 */ 95 @MainThread initialize(final Context context)96 public static void initialize(final Context context) { 97 if (sInitialized) { 98 return; 99 } 100 sInitialized = true; 101 if (useContentProvider(context)) { 102 loadPreferences(context); 103 sContentObserver = new ContentObserver(new Handler()) { 104 @Override 105 public void onChange(boolean selfChange) { 106 loadPreferences(context); 107 } 108 }; 109 context.getContentResolver().registerContentObserver( 110 TunerPreferenceProvider.Preferences.CONTENT_URI, true, sContentObserver); 111 } else { 112 new AsyncTask<Void, Void, Void>() { 113 @Override 114 protected Void doInBackground(Void... params) { 115 getSharedPreferences(context); 116 return null; 117 } 118 }.execute(); 119 } 120 } 121 122 /** 123 * Releases the resources. 124 */ release(Context context)125 public static synchronized void release(Context context) { 126 if (useContentProvider(context) && sContentObserver != null) { 127 context.getContentResolver().unregisterContentObserver(sContentObserver); 128 } 129 setTunerPreferencesChangedListener(null); 130 } 131 132 /** 133 * Sets the listener for TunerPreferences change. 134 */ setTunerPreferencesChangedListener( TunerPreferencesChangedListener listener)135 public static void setTunerPreferencesChangedListener( 136 TunerPreferencesChangedListener listener) { 137 sPreferencesChangedListener = listener; 138 } 139 140 /** 141 * Loads the preferences from database. 142 * <p> 143 * This preferences is used across processes, so the preferences should be loaded again when the 144 * databases changes. 145 */ 146 @MainThread loadPreferences(Context context)147 public static void loadPreferences(Context context) { 148 if (sLoadPreferencesTask != null 149 && sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) { 150 sLoadPreferencesTask.cancel(true); 151 } 152 sLoadPreferencesTask = new LoadPreferencesTask(context); 153 sLoadPreferencesTask.execute(); 154 } 155 useContentProvider(Context context)156 private static boolean useContentProvider(Context context) { 157 // If TIS is a part of LC, it should use ContentProvider to resolve multiple process access. 158 return TisConfiguration.isPackagedWithLiveChannels(context); 159 } 160 getChannelDataVersion(Context context)161 public static synchronized int getChannelDataVersion(Context context) { 162 SoftPreconditions.checkState(sInitialized); 163 if (useContentProvider(context)) { 164 return sPreferenceValues.getInt(PREFS_KEY_CHANNEL_DATA_VERSION, 165 CHANNEL_DATA_VERSION_NOT_SET); 166 } else { 167 return getSharedPreferences(context) 168 .getInt(TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION, 169 CHANNEL_DATA_VERSION_NOT_SET); 170 } 171 } 172 setChannelDataVersion(Context context, int version)173 public static synchronized void setChannelDataVersion(Context context, int version) { 174 if (useContentProvider(context)) { 175 setPreference(context, PREFS_KEY_CHANNEL_DATA_VERSION, version); 176 } else { 177 getSharedPreferences(context).edit() 178 .putInt(TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION, version) 179 .apply(); 180 } 181 } 182 getScannedChannelCount(Context context)183 public static synchronized int getScannedChannelCount(Context context) { 184 SoftPreconditions.checkState(sInitialized); 185 if (useContentProvider(context)) { 186 return sPreferenceValues.getInt(PREFS_KEY_SCANNED_CHANNEL_COUNT); 187 } else { 188 return getSharedPreferences(context) 189 .getInt(TunerPreferences.PREFS_KEY_SCANNED_CHANNEL_COUNT, 0); 190 } 191 } 192 setScannedChannelCount(Context context, int channelCount)193 public static synchronized void setScannedChannelCount(Context context, int channelCount) { 194 if (useContentProvider(context)) { 195 setPreference(context, PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount); 196 } else { 197 getSharedPreferences(context).edit() 198 .putInt(TunerPreferences.PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount) 199 .apply(); 200 } 201 } 202 getLastPostalCode(Context context)203 public static synchronized String getLastPostalCode(Context context) { 204 SoftPreconditions.checkState(sInitialized); 205 if (useContentProvider(context)) { 206 return sPreferenceValues.getString(PREFS_KEY_LAST_POSTAL_CODE); 207 } else { 208 return getSharedPreferences(context).getString(PREFS_KEY_LAST_POSTAL_CODE, null); 209 } 210 } 211 setLastPostalCode(Context context, String postalCode)212 public static synchronized void setLastPostalCode(Context context, String postalCode) { 213 if (useContentProvider(context)) { 214 setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode); 215 } else { 216 getSharedPreferences(context).edit() 217 .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode).apply(); 218 } 219 } 220 isScanDone(Context context)221 public static synchronized boolean isScanDone(Context context) { 222 SoftPreconditions.checkState(sInitialized); 223 if (useContentProvider(context)) { 224 return sPreferenceValues.getBoolean(PREFS_KEY_SCAN_DONE); 225 } else { 226 return getSharedPreferences(context) 227 .getBoolean(TunerPreferences.PREFS_KEY_SCAN_DONE, false); 228 } 229 } 230 setScanDone(Context context)231 public static synchronized void setScanDone(Context context) { 232 if (useContentProvider(context)) { 233 setPreference(context, PREFS_KEY_SCAN_DONE, true); 234 } else { 235 getSharedPreferences(context).edit() 236 .putBoolean(TunerPreferences.PREFS_KEY_SCAN_DONE, true) 237 .apply(); 238 } 239 } 240 shouldShowSetupActivity(Context context)241 public static synchronized boolean shouldShowSetupActivity(Context context) { 242 SoftPreconditions.checkState(sInitialized); 243 if (useContentProvider(context)) { 244 return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP); 245 } else { 246 return getSharedPreferences(context) 247 .getBoolean(TunerPreferences.PREFS_KEY_LAUNCH_SETUP, false); 248 } 249 } 250 setShouldShowSetupActivity(Context context, boolean need)251 public static synchronized void setShouldShowSetupActivity(Context context, boolean need) { 252 if (useContentProvider(context)) { 253 setPreference(context, PREFS_KEY_LAUNCH_SETUP, need); 254 } else { 255 getSharedPreferences(context).edit() 256 .putBoolean(TunerPreferences.PREFS_KEY_LAUNCH_SETUP, need) 257 .apply(); 258 } 259 } 260 getTrickplayExpiredMs(Context context)261 public static synchronized long getTrickplayExpiredMs(Context context) { 262 SoftPreconditions.checkState(sInitialized); 263 if (useContentProvider(context)) { 264 return sPreferenceValues.getLong(PREFS_KEY_TRICKPLAY_EXPIRED_MS, 0); 265 } else { 266 return getSharedPreferences(context) 267 .getLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, 0); 268 } 269 } 270 setTrickplayExpiredMs(Context context, long timeMs)271 public static synchronized void setTrickplayExpiredMs(Context context, long timeMs) { 272 if (useContentProvider(context)) { 273 setPreference(context, PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs); 274 } else { 275 getSharedPreferences(context).edit() 276 .putLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs) 277 .apply(); 278 } 279 } 280 getTrickplaySetting(Context context)281 public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) { 282 SoftPreconditions.checkState(sInitialized); 283 if (useContentProvider(context)) { 284 return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); 285 } else { 286 return getSharedPreferences(context) 287 .getInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); 288 } 289 } 290 setTrickplaySetting(Context context, @TrickplaySetting int trickplaySetting)291 public static synchronized void setTrickplaySetting(Context context, 292 @TrickplaySetting int trickplaySetting) { 293 SoftPreconditions.checkState(sInitialized); 294 SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET); 295 if (useContentProvider(context)) { 296 setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting); 297 } else { 298 getSharedPreferences(context).edit() 299 .putInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting) 300 .apply(); 301 } 302 } 303 getStoreTsStream(Context context)304 public static synchronized boolean getStoreTsStream(Context context) { 305 SoftPreconditions.checkState(sInitialized); 306 if (useContentProvider(context)) { 307 return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false); 308 } else { 309 return getSharedPreferences(context) 310 .getBoolean(TunerPreferences.PREFS_KEY_STORE_TS_STREAM, false); 311 } 312 } 313 setStoreTsStream(Context context, boolean shouldStore)314 public static synchronized void setStoreTsStream(Context context, boolean shouldStore) { 315 if (useContentProvider(context)) { 316 setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore); 317 } else { 318 getSharedPreferences(context).edit() 319 .putBoolean(TunerPreferences.PREFS_KEY_STORE_TS_STREAM, shouldStore) 320 .apply(); 321 } 322 } 323 getSharedPreferences(Context context)324 private static SharedPreferences getSharedPreferences(Context context) { 325 return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE); 326 } 327 setPreference(Context context, String key, String value)328 private static synchronized void setPreference(Context context, String key, String value) { 329 sPreferenceValues.putString(key, value); 330 savePreference(context, key, value); 331 } 332 setPreference(Context context, String key, int value)333 private static synchronized void setPreference(Context context, String key, int value) { 334 sPreferenceValues.putInt(key, value); 335 savePreference(context, key, Integer.toString(value)); 336 } 337 setPreference(Context context, String key, long value)338 private static synchronized void setPreference(Context context, String key, long value) { 339 sPreferenceValues.putLong(key, value); 340 savePreference(context, key, Long.toString(value)); 341 } 342 setPreference(Context context, String key, boolean value)343 private static synchronized void setPreference(Context context, String key, boolean value) { 344 sPreferenceValues.putBoolean(key, value); 345 savePreference(context, key, Boolean.toString(value)); 346 } 347 savePreference(final Context context, final String key, final String value)348 private static void savePreference(final Context context, final String key, 349 final String value) { 350 new AsyncTask<Void, Void, Void>() { 351 @Override 352 protected Void doInBackground(Void... params) { 353 ContentResolver resolver = context.getContentResolver(); 354 ContentValues values = new ContentValues(); 355 values.put(Preferences.COLUMN_KEY, key); 356 values.put(Preferences.COLUMN_VALUE, value); 357 try { 358 resolver.insert(Preferences.CONTENT_URI, values); 359 } catch (Exception e) { 360 SoftPreconditions.warn(TAG, "setPreference", "Error writing preference values", 361 e); 362 } 363 return null; 364 } 365 }.execute(); 366 } 367 368 private static class LoadPreferencesTask extends AsyncTask<Void, Void, Bundle> { 369 private final Context mContext; LoadPreferencesTask(Context context)370 private LoadPreferencesTask(Context context) { 371 mContext = context; 372 } 373 374 @Override doInBackground(Void... params)375 protected Bundle doInBackground(Void... params) { 376 Bundle bundle = new Bundle(); 377 ContentResolver resolver = mContext.getContentResolver(); 378 String[] projection = new String[] { Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE }; 379 try (Cursor cursor = resolver.query(Preferences.CONTENT_URI, projection, null, null, 380 null)) { 381 if (cursor != null) { 382 while (!isCancelled() && cursor.moveToNext()) { 383 String key = cursor.getString(0); 384 String value = cursor.getString(1); 385 switch (key) { 386 case PREFS_KEY_TRICKPLAY_EXPIRED_MS: 387 bundle.putLong(key, Long.parseLong(value)); 388 break; 389 case PREFS_KEY_CHANNEL_DATA_VERSION: 390 case PREFS_KEY_SCANNED_CHANNEL_COUNT: 391 case PREFS_KEY_TRICKPLAY_SETTING: 392 try { 393 bundle.putInt(key, Integer.parseInt(value)); 394 } catch (NumberFormatException e) { 395 // Does nothing. 396 } 397 break; 398 case PREFS_KEY_SCAN_DONE: 399 case PREFS_KEY_LAUNCH_SETUP: 400 case PREFS_KEY_STORE_TS_STREAM: 401 bundle.putBoolean(key, Boolean.parseBoolean(value)); 402 break; 403 case PREFS_KEY_LAST_POSTAL_CODE: 404 bundle.putString(key, value); 405 break; 406 } 407 } 408 } 409 } catch (Exception e) { 410 SoftPreconditions.warn(TAG, "getPreference", "Error querying preference values", e); 411 return null; 412 } 413 return bundle; 414 } 415 416 @Override onPostExecute(Bundle bundle)417 protected void onPostExecute(Bundle bundle) { 418 synchronized (TunerPreferences.class) { 419 if (bundle != null) { 420 sPreferenceValues.putAll(bundle); 421 } 422 } 423 if (sPreferencesChangedListener != null) { 424 sPreferencesChangedListener.onTunerPreferencesChanged(); 425 } 426 } 427 } 428 }