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 return preferences.getString(key, defaultValue); 324 } 325 326 /** 327 * Retrieve a setting's value as a String, using the default value 328 * stored in the DefaultsStore. 329 */ 330 public String getString(String scope, String key) { 331 return getString(scope, key, getStringDefault(key)); 332 } 333 334 /** 335 * Retrieve a setting's value as an Integer, manually specifiying 336 * a default value. 337 */ 338 public Integer getInteger(String scope, String key, Integer defaultValue) { 339 String defaultValueString = Integer.toString(defaultValue); 340 String value = getString(scope, key, defaultValueString); 341 return Integer.parseInt(value); 342 } 343 344 /** 345 * Retrieve a setting's value as an Integer, converting the default value 346 * stored in the DefaultsStore. 347 */ 348 public Integer getInteger(String scope, String key) { 349 return getInteger(scope, key, getIntegerDefault(key)); 350 } 351 352 /** 353 * Retrieve a setting's value as a boolean, manually specifiying 354 * a default value. 355 */ 356 public boolean getBoolean(String scope, String key, boolean defaultValue) { 357 String defaultValueString = defaultValue ? "1" : "0"; 358 String value = getString(scope, key, defaultValueString); 359 return (Integer.parseInt(value) != 0); 360 } 361 362 /** 363 * Retrieve a setting's value as a boolean, converting the default value 364 * stored in the DefaultsStore. 365 */ 366 public boolean getBoolean(String scope, String key) { 367 return getBoolean(scope, key, getBooleanDefault(key)); 368 } 369 370 /** 371 * Retrieve a setting's value as a {@link Size}. Returns <code>null</code> 372 * if value could not be parsed as a size. 373 */ 374 public Size getSize(String scope, String key) { 375 String strValue = getString(scope, key); 376 if (strValue == null) { 377 return null; 378 } 379 380 String[] widthHeight = strValue.split("x"); 381 if (widthHeight.length != 2) { 382 return null; 383 } 384 385 try { 386 int width = Integer.parseInt(widthHeight[0]); 387 int height = Integer.parseInt(widthHeight[1]); 388 return new Size(width, height); 389 } catch (NumberFormatException ex) { 390 return null; 391 } 392 } 393 394 /** 395 * If possible values are stored for this key, return the 396 * index into that list of the currently set value. 397 * 398 * For example, if a set of possible values is [2,3,5], 399 * and the current value set of this key is 3, this method 400 * returns 1. 401 * 402 * If possible values are not stored for this key, throw 403 * an IllegalArgumentException. 404 */ 405 public int getIndexOfCurrentValue(String scope, String key) { 406 String[] possibleValues = mDefaultsStore.getPossibleValues(key); 407 if (possibleValues == null || possibleValues.length == 0) { 408 throw new IllegalArgumentException( 409 "No possible values for scope=" + scope + " key=" + key); 410 } 411 412 String value = getString(scope, key); 413 for (int i = 0; i < possibleValues.length; i++) { 414 if (value.equals(possibleValues[i])) { 415 return i; 416 } 417 } 418 throw new IllegalStateException("Current value for scope=" + scope + " key=" 419 + key + " not in list of possible values"); 420 } 421 422 /** 423 * Store a setting's value using a String value. No conversion 424 * occurs before this value is stored in SharedPreferences. 425 */ 426 public void set(String scope, String key, String value) { 427 SharedPreferences preferences = getPreferencesFromScope(scope); 428 preferences.edit().putString(key, value).apply(); 429 } 430 431 /** 432 * Store a setting's value using an Integer value. Type conversion 433 * to String occurs before this value is stored in SharedPreferences. 434 */ 435 public void set(String scope, String key, int value) { 436 set(scope, key, convert(value)); 437 } 438 439 /** 440 * Store a setting's value using a boolean value. Type conversion 441 * to an Integer and then to a String occurs before this value is 442 * stored in SharedPreferences. 443 */ 444 public void set(String scope, String key, boolean value) { 445 set(scope, key, convert(value)); 446 } 447 448 /** 449 * Set a setting to the default value stored in the DefaultsStore. 450 */ 451 public void setToDefault(String scope, String key) { 452 set(scope, key, getStringDefault(key)); 453 } 454 455 /** 456 * If a set of possible values is defined, set the current value 457 * of a setting to the possible value found at the given index. 458 * 459 * For example, if the possible values for a key are [2,3,5], 460 * and the index given to this method is 2, then this method would 461 * store the value 5 in SharedPreferences for the key. 462 * 463 * If the index is out of the bounds of the range of possible values, 464 * or there are no possible values for this key, then this 465 * method throws an exception. 466 */ 467 public void setValueByIndex(String scope, String key, int index) { 468 String[] possibleValues = mDefaultsStore.getPossibleValues(key); 469 if (possibleValues.length == 0) { 470 throw new IllegalArgumentException( 471 "No possible values for scope=" + scope + " key=" + key); 472 } 473 474 if (index >= 0 && index < possibleValues.length) { 475 set(scope, key, possibleValues[index]); 476 } else { 477 throw new IndexOutOfBoundsException("For possible values of scope=" + scope 478 + " key=" + key); 479 } 480 } 481 482 /** 483 * Check that a setting has some value stored. 484 */ 485 public boolean isSet(String scope, String key) { 486 SharedPreferences preferences = getPreferencesFromScope(scope); 487 return preferences.contains(key); 488 } 489 490 /** 491 * Check whether a settings's value is currently set to the 492 * default value. 493 */ 494 public boolean isDefault(String scope, String key) { 495 String defaultValue = getStringDefault(key); 496 String value = getString(scope, key); 497 return value == null ? false : value.equals(defaultValue); 498 } 499 500 /** 501 * Remove a setting. 502 */ 503 public void remove(String scope, String key) { 504 SharedPreferences preferences = getPreferencesFromScope(scope); 505 preferences.edit().remove(key).apply(); 506 } 507 508 /** 509 * Package private conversion method to turn ints into preferred 510 * String storage format. 511 * 512 * @param value int to be stored in Settings 513 * @return String which represents the int 514 */ 515 static String convert(int value) { 516 return Integer.toString(value); 517 } 518 519 /** 520 * Package private conversion method to turn booleans into preferred 521 * String storage format. 522 * 523 * @param value boolean to be stored in Settings 524 * @return String which represents the boolean 525 */ 526 static String convert(boolean value) { 527 return value ? "1" : "0"; 528 } 529 } 530