1 /* 2 * Copyright (C) 2013 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.camera.settings; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 22 import android.preference.PreferenceManager; 23 24 import com.android.camera.debug.Log; 25 import com.android.camera.util.Size; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 30 /** 31 * SettingsManager class provides an api for getting and setting SharedPreferences 32 * values. 33 * 34 * Types 35 * 36 * This API simplifies settings type management by storing all settings values 37 * in SharedPreferences as Strings. To do this, the API to converts boolean and 38 * Integer values to Strings when those values are stored, making the conversion 39 * back to a boolean or Integer also consistent and simple. 40 * 41 * This also enables the user to safely get settings values as three different types, 42 * as it's convenient: String, Integer, and boolean values. Integers and boolean 43 * can always be trivially converted to one another, but Strings cannot always be 44 * parsed as Integers. In this case, if the user stores a String value that cannot 45 * be parsed to an Integer yet they try to retrieve it as an Integer, the API throws 46 * a meaningful exception to the user. 47 * 48 * Scope 49 * 50 * This API introduces the concept of "scope" for a setting, which is the generality 51 * of a setting. The most general settings, that can be accessed acrossed the 52 * entire application, have a scope of SCOPE_GLOBAL. They are stored in the default 53 * SharedPreferences file. 54 * 55 * A setting that is local to a third party module or subset of the application has 56 * a custom scope. The specific module can define whatever scope (String) argument 57 * they want, and the settings saved with that scope can only be seen by that third 58 * party module. Scope is a general concept that helps protect settings values 59 * from being clobbered in different contexts. 60 * 61 * Keys and Defaults 62 * 63 * This API allows you to store your SharedPreferences keys and default values 64 * outside the SettingsManager, because these values are either passed into 65 * the API or stored in a cache when the user sets defaults. 66 * 67 * For any setting, it is optional to store a default or set of possible values, 68 * unless you plan on using the getIndexOfCurrentValue and setValueByIndex, 69 * methods, which rely on an index into the set of possible values. 70 * 71 */ 72 public class SettingsManager { 73 private static final Log.Tag TAG = new Log.Tag("SettingsManager"); 74 75 private final Context mContext; 76 private final String mPackageName; 77 private final SharedPreferences mDefaultPreferences; 78 private SharedPreferences mCustomPreferences; 79 private final DefaultsStore mDefaultsStore = new DefaultsStore(); 80 81 /** 82 * A List of OnSettingChangedListener's, maintained to compare to new 83 * listeners and prevent duplicate registering. 84 */ 85 private final List<OnSettingChangedListener> mListeners = 86 new ArrayList<OnSettingChangedListener>(); 87 88 /** 89 * A List of OnSharedPreferenceChangeListener's, maintained to hold pointers 90 * to actually registered listeners, so they can be unregistered. 91 */ 92 private final List<OnSharedPreferenceChangeListener> mSharedPreferenceListeners = 93 new ArrayList<OnSharedPreferenceChangeListener>(); 94 95 public SettingsManager(Context context) { 96 mContext = context; 97 mPackageName = mContext.getPackageName(); 98 99 mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); 100 } 101 102 /** 103 * Get the SettingsManager's default preferences. This is useful 104 * to third party modules as they are defining their upgrade paths, 105 * since most third party modules will use either SCOPE_GLOBAL or a 106 * custom scope. 107 */ 108 public SharedPreferences getDefaultPreferences() { 109 return mDefaultPreferences; 110 } 111 112 /** 113 * Open a SharedPreferences file by custom scope. 114 * Also registers any known SharedPreferenceListeners on this 115 * SharedPreferences instance. 116 */ 117 protected SharedPreferences openPreferences(String scope) { 118 SharedPreferences preferences = mContext.getSharedPreferences( 119 mPackageName + scope, Context.MODE_PRIVATE); 120 121 for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) { 122 preferences.registerOnSharedPreferenceChangeListener(listener); 123 } 124 125 return preferences; 126 } 127 128 /** 129 * Close a SharedPreferences file by custom scope. 130 * The file isn't explicitly closed (the SharedPreferences API makes 131 * this unnecessary), so the real work is to unregister any known 132 * SharedPreferenceListeners from this SharedPreferences instance. 133 * 134 * It's important to do this as camera and modules change, because 135 * we don't want old SharedPreferences listeners executing on 136 * cameras/modules they are not compatible with. 137 */ 138 protected void closePreferences(SharedPreferences preferences) { 139 for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) { 140 preferences.unregisterOnSharedPreferenceChangeListener(listener); 141 } 142 } 143 144 /** 145 * Interface with Camera Device Settings and Modules. 146 */ 147 public interface OnSettingChangedListener { 148 /** 149 * Called every time a SharedPreference has been changed. 150 */ 151 public void onSettingChanged(SettingsManager settingsManager, String key); 152 } 153 154 private OnSharedPreferenceChangeListener getSharedPreferenceListener( 155 final OnSettingChangedListener listener) { 156 return new OnSharedPreferenceChangeListener() { 157 @Override 158 public void onSharedPreferenceChanged( 159 SharedPreferences sharedPreferences, String key) { 160 listener.onSettingChanged(SettingsManager.this, key); 161 } 162 }; 163 } 164 165 /** 166 * Add an OnSettingChangedListener to the SettingsManager, which will 167 * execute onSettingsChanged when any SharedPreference has been updated. 168 */ 169 public void addListener(final OnSettingChangedListener listener) { 170 if (listener == null) { 171 throw new IllegalArgumentException("OnSettingChangedListener cannot be null."); 172 } 173 174 if (mListeners.contains(listener)) { 175 return; 176 } 177 178 mListeners.add(listener); 179 OnSharedPreferenceChangeListener sharedPreferenceListener = 180 getSharedPreferenceListener(listener); 181 mSharedPreferenceListeners.add(sharedPreferenceListener); 182 mDefaultPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceListener); 183 184 if (mCustomPreferences != null) { 185 mCustomPreferences.registerOnSharedPreferenceChangeListener( 186 sharedPreferenceListener); 187 } 188 Log.v(TAG, "listeners: " + mListeners); 189 } 190 191 /** 192 * Remove a specific SettingsListener. This should be done in onPause if a 193 * listener has been set. 194 */ 195 public void removeListener(OnSettingChangedListener listener) { 196 if (listener == null) { 197 throw new IllegalArgumentException(); 198 } 199 200 if (!mListeners.contains(listener)) { 201 return; 202 } 203 204 int index = mListeners.indexOf(listener); 205 mListeners.remove(listener); 206 207 OnSharedPreferenceChangeListener sharedPreferenceListener = 208 mSharedPreferenceListeners.get(index); 209 mSharedPreferenceListeners.remove(index); 210 mDefaultPreferences.unregisterOnSharedPreferenceChangeListener( 211 sharedPreferenceListener); 212 213 if (mCustomPreferences != null) { 214 mCustomPreferences.unregisterOnSharedPreferenceChangeListener( 215 sharedPreferenceListener); 216 } 217 } 218 219 /** 220 * Remove all OnSharedPreferenceChangedListener's. This should be done in 221 * onDestroy. 222 */ 223 public void removeAllListeners() { 224 for (OnSharedPreferenceChangeListener listener : mSharedPreferenceListeners) { 225 mDefaultPreferences.unregisterOnSharedPreferenceChangeListener(listener); 226 227 if (mCustomPreferences != null) { 228 mCustomPreferences.unregisterOnSharedPreferenceChangeListener(listener); 229 } 230 } 231 mSharedPreferenceListeners.clear(); 232 mListeners.clear(); 233 } 234 235 /** This scope stores and retrieves settings from 236 default preferences. */ 237 public static final String SCOPE_GLOBAL = "default_scope"; 238 239 /** 240 * Returns the SharedPreferences file matching the scope 241 * argument. 242 * 243 * Camera and module preferences files are cached, 244 * until the camera id or module id changes, then the listeners 245 * are unregistered and a new file is opened. 246 */ 247 private SharedPreferences getPreferencesFromScope(String scope) { 248 if (scope.equals(SCOPE_GLOBAL)) { 249 return mDefaultPreferences; 250 } 251 252 if (mCustomPreferences != null) { 253 closePreferences(mCustomPreferences); 254 } 255 mCustomPreferences = openPreferences(scope); 256 return mCustomPreferences; 257 } 258 259 /** 260 * Set default and valid values for a setting, for a String default and 261 * a set of String possible values that are already defined. 262 * This is not required. 263 */ 264 public void setDefaults(String key, String defaultValue, String[] possibleValues) { 265 mDefaultsStore.storeDefaults(key, defaultValue, possibleValues); 266 } 267 268 /** 269 * Set default and valid values for a setting, for an Integer default and 270 * a set of Integer possible values that are already defined. 271 * This is not required. 272 */ 273 public void setDefaults(String key, int defaultValue, int[] possibleValues) { 274 String defaultValueString = Integer.toString(defaultValue); 275 String[] possibleValuesString = new String[possibleValues.length]; 276 for (int i = 0; i < possibleValues.length; i++) { 277 possibleValuesString[i] = Integer.toString(possibleValues[i]); 278 } 279 mDefaultsStore.storeDefaults(key, defaultValueString, possibleValuesString); 280 } 281 282 /** 283 * Set default and valid values for a setting, for a boolean default. 284 * The set of boolean possible values is always { false, true }. 285 * This is not required. 286 */ 287 public void setDefaults(String key, boolean defaultValue) { 288 String defaultValueString = defaultValue ? "1" : "0"; 289 String[] possibleValues = { "0", "1" }; 290 mDefaultsStore.storeDefaults(key, defaultValueString, possibleValues); 291 } 292 293 /** 294 * Retrieve a default from the DefaultsStore as a String. 295 */ 296 public String getStringDefault(String key) { 297 return mDefaultsStore.getDefaultValue(key); 298 } 299 300 /** 301 * Retrieve a default from the DefaultsStore as an Integer. 302 */ 303 public Integer getIntegerDefault(String key) { 304 String defaultValueString = mDefaultsStore.getDefaultValue(key); 305 return defaultValueString == null ? 0 : Integer.parseInt(defaultValueString); 306 } 307 308 /** 309 * Retrieve a default from the DefaultsStore as a boolean. 310 */ 311 public boolean getBooleanDefault(String key) { 312 String defaultValueString = mDefaultsStore.getDefaultValue(key); 313 return defaultValueString == null ? false : 314 (Integer.parseInt(defaultValueString) != 0); 315 } 316 317 /** 318 * Retrieve a setting's value as a String, manually specifiying 319 * a default value. 320 */ 321 public String getString(String scope, String key, String defaultValue) { 322 SharedPreferences preferences = getPreferencesFromScope(scope); 323 try { 324 return preferences.getString(key, defaultValue); 325 } catch (ClassCastException e) { 326 Log.w(TAG, "existing preference with invalid type, removing and returning default", e); 327 preferences.edit().remove(key).apply(); 328 return defaultValue; 329 } 330 } 331 332 /** 333 * Retrieve a setting's value as a String, using the default value 334 * stored in the DefaultsStore. 335 */ 336 public String getString(String scope, String key) { 337 return getString(scope, key, getStringDefault(key)); 338 } 339 340 /** 341 * Retrieve a setting's value as an Integer, manually specifying 342 * a default value. 343 */ 344 public Integer getInteger(String scope, String key, Integer defaultValue) { 345 String defaultValueString = Integer.toString(defaultValue); 346 String value = getString(scope, key, defaultValueString); 347 return convertToInt(value); 348 } 349 350 /** 351 * Retrieve a setting's value as an Integer, converting the default value 352 * stored in the DefaultsStore. 353 */ 354 public Integer getInteger(String scope, String key) { 355 return getInteger(scope, key, getIntegerDefault(key)); 356 } 357 358 /** 359 * Retrieve a setting's value as a boolean, manually specifiying 360 * a default value. 361 */ 362 public boolean getBoolean(String scope, String key, boolean defaultValue) { 363 String defaultValueString = defaultValue ? "1" : "0"; 364 String value = getString(scope, key, defaultValueString); 365 return convertToBoolean(value); 366 } 367 368 /** 369 * Retrieve a setting's value as a boolean, converting the default value 370 * stored in the DefaultsStore. 371 */ 372 public boolean getBoolean(String scope, String key) { 373 return getBoolean(scope, key, getBooleanDefault(key)); 374 } 375 376 /** 377 * Retrieve a setting's value as a {@link Size}. Returns <code>null</code> 378 * if value could not be parsed as a size. 379 */ 380 public Size getSize(String scope, String key) { 381 String strValue = getString(scope, key); 382 if (strValue == null) { 383 return null; 384 } 385 386 String[] widthHeight = strValue.split("x"); 387 if (widthHeight.length != 2) { 388 return null; 389 } 390 391 try { 392 int width = Integer.parseInt(widthHeight[0]); 393 int height = Integer.parseInt(widthHeight[1]); 394 return new Size(width, height); 395 } catch (NumberFormatException ex) { 396 return null; 397 } 398 } 399 400 /** 401 * If possible values are stored for this key, return the 402 * index into that list of the currently set value. 403 * 404 * For example, if a set of possible values is [2,3,5], 405 * and the current value set of this key is 3, this method 406 * returns 1. 407 * 408 * If possible values are not stored for this key, throw 409 * an IllegalArgumentException. 410 */ 411 public int getIndexOfCurrentValue(String scope, String key) { 412 String[] possibleValues = mDefaultsStore.getPossibleValues(key); 413 if (possibleValues == null || possibleValues.length == 0) { 414 throw new IllegalArgumentException( 415 "No possible values for scope=" + scope + " key=" + key); 416 } 417 418 String value = getString(scope, key); 419 for (int i = 0; i < possibleValues.length; i++) { 420 if (value.equals(possibleValues[i])) { 421 return i; 422 } 423 } 424 throw new IllegalStateException("Current value for scope=" + scope + " key=" 425 + key + " not in list of possible values"); 426 } 427 428 /** 429 * Store a setting's value using a String value. No conversion 430 * occurs before this value is stored in SharedPreferences. 431 */ 432 public void set(String scope, String key, String value) { 433 SharedPreferences preferences = getPreferencesFromScope(scope); 434 preferences.edit().putString(key, value).apply(); 435 } 436 437 /** 438 * Store a setting's value using an Integer value. Type conversion 439 * to String occurs before this value is stored in SharedPreferences. 440 */ 441 public void set(String scope, String key, int value) { 442 set(scope, key, convert(value)); 443 } 444 445 /** 446 * Store a setting's value using a boolean value. Type conversion 447 * to an Integer and then to a String occurs before this value is 448 * stored in SharedPreferences. 449 */ 450 public void set(String scope, String key, boolean value) { 451 set(scope, key, convert(value)); 452 } 453 454 /** 455 * Set a setting to the default value stored in the DefaultsStore. 456 */ 457 public void setToDefault(String scope, String key) { 458 set(scope, key, getStringDefault(key)); 459 } 460 461 /** 462 * If a set of possible values is defined, set the current value 463 * of a setting to the possible value found at the given index. 464 * 465 * For example, if the possible values for a key are [2,3,5], 466 * and the index given to this method is 2, then this method would 467 * store the value 5 in SharedPreferences for the key. 468 * 469 * If the index is out of the bounds of the range of possible values, 470 * or there are no possible values for this key, then this 471 * method throws an exception. 472 */ 473 public void setValueByIndex(String scope, String key, int index) { 474 String[] possibleValues = mDefaultsStore.getPossibleValues(key); 475 if (possibleValues.length == 0) { 476 throw new IllegalArgumentException( 477 "No possible values for scope=" + scope + " key=" + key); 478 } 479 480 if (index >= 0 && index < possibleValues.length) { 481 set(scope, key, possibleValues[index]); 482 } else { 483 throw new IndexOutOfBoundsException("For possible values of scope=" + scope 484 + " key=" + key); 485 } 486 } 487 488 /** 489 * Check that a setting has some value stored. 490 */ 491 public boolean isSet(String scope, String key) { 492 SharedPreferences preferences = getPreferencesFromScope(scope); 493 return preferences.contains(key); 494 } 495 496 /** 497 * Check whether a settings's value is currently set to the 498 * default value. 499 */ 500 public boolean isDefault(String scope, String key) { 501 String defaultValue = getStringDefault(key); 502 String value = getString(scope, key); 503 return value == null ? false : value.equals(defaultValue); 504 } 505 506 /** 507 * Remove a setting. 508 */ 509 public void remove(String scope, String key) { 510 SharedPreferences preferences = getPreferencesFromScope(scope); 511 preferences.edit().remove(key).apply(); 512 } 513 514 /** 515 * Package private conversion method to turn ints into preferred 516 * String storage format. 517 * 518 * @param value int to be stored in Settings 519 * @return String which represents the int 520 */ 521 static String convert(int value) { 522 return Integer.toString(value); 523 } 524 525 /** 526 * Package private conversion method to turn String storage format into 527 * ints. 528 * 529 * @param value String to be converted to int 530 * @return int value of stored String 531 */ 532 static int convertToInt(String value) { 533 return Integer.parseInt(value); 534 } 535 536 /** 537 * Package private conversion method to turn String storage format into 538 * booleans. 539 * 540 * @param value String to be converted to boolean 541 * @return boolean value of stored String 542 */ 543 static boolean convertToBoolean(String value) { 544 return Integer.parseInt(value) != 0; 545 } 546 547 548 /** 549 * Package private conversion method to turn booleans into preferred 550 * String storage format. 551 * 552 * @param value boolean to be stored in Settings 553 * @return String which represents the boolean 554 */ 555 static String convert(boolean value) { 556 return value ? "1" : "0"; 557 } 558 } 559