Java tutorial
/** * Copyright (c) 2013 Robert Nyholm. All rights reserved. */ package ax.ha.it.smsalarm.handler; import java.util.ArrayList; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.util.Log; import ax.ha.it.smsalarm.application.SmsAlarmApplication.GoogleAnalyticsHandler; import ax.ha.it.smsalarm.application.SmsAlarmApplication.GoogleAnalyticsHandler.ReportRule; /** * Class responsible for all {@link SharedPreferences} handling.<br> * <b><i>PreferencesHandler is a singleton, eagerly initialized to avoid concurrent modification.</i></b> * * @author Robert Nyholm <robert.nyholm@aland.net> * @version 2.3.1 * @since 2.0 */ public class SharedPreferencesHandler { /** * The different valid data types, for storing and fetching {@link SharedPreferences}. * * @author Robert Nyholm <robert.nyholm@aland.net> * @version 2.3.1 * @since 2.1 */ public enum DataType { INTEGER, STRING, BOOLEAN, LIST; } /** * The different valid {@link SharedPreferences}. * * @author Robert Nyholm <robert.nyholm@aland.net> * @version 2.3.1 * @since 2.1 */ public enum PrefKey { // @formatter:off SHARED_PREF("smsAlarmPrefs", ReportRule.NO_REPORT, "Shared preferences main key"), PRIMARY_LISTEN_NUMBER_KEY("primaryListenNumberKey", ReportRule.NO_REPORT, "Primary listen number"), // Not used after version code 8 PRIMARY_LISTEN_NUMBERS_KEY("primaryListenNumbersKey", ReportRule.REPORT_ANONYMIZE, "Primary alarm triggering phone numbers used"), SECONDARY_LISTEN_NUMBERS_KEY( "secondaryListenNumbersKey", ReportRule.REPORT_ANONYMIZE, "Secondary alarm triggering phone numbers used"), PRIMARY_LISTEN_FREE_TEXTS_KEY( "primaryListenFreeTextsKey", ReportRule.REPORT_ANONYMIZE, "Primary alarm triggering words used"), SECONDARY_LISTEN_FREE_TEXTS_KEY( "secondaryListenFreeTextsKey", ReportRule.REPORT_ANONYMIZE, "Secondary alarm triggering words used"), PRIMARY_MESSAGE_TONE_KEY( "primaryMessageToneKey", ReportRule.NO_REPORT, "Primary message tone"), // Not used after version code 13 SECONDARY_MESSAGE_TONE_KEY("secondaryMessageToneKey", ReportRule.NO_REPORT, "Secondary message tone"), // Not used after version code 13 ENABLE_ACK_KEY("enableAckKey", ReportRule.REPORT_RAW, "Enable acknowledgement"), ACK_NUMBER_KEY("ackNumber", ReportRule.NO_REPORT, "Phone number for acknowledgement"), ACK_MESSAGE_KEY("ackMessageKey", ReportRule.NO_REPORT, "Message for acknowledgement"), ACK_METHOD_KEY("ackMethodKey", ReportRule.REPORT_RAW, "Method for acknowledgement"), USE_OS_SOUND_SETTINGS_KEY( "useOsSoundSettings", ReportRule.REPORT_RAW, "Use operating systems sound settings"), PLAY_TONE_TWICE_KEY( "playToneTwice", ReportRule.NO_REPORT, "Play tone twice"), // Not used after version code 13 PLAY_ALARM_SIGNAL_TWICE_KEY("playAlarmSignalTwice", ReportRule.REPORT_RAW, "Play alarm signal twice"), PLAY_ALARM_SIGNAL_REPEATEDLY_KEY("playAlarmSignalRepeatedly", ReportRule.REPORT_RAW, "Play alarm signal repeatedly"), ENABLE_SMS_ALARM_KEY( "enableSmsAlarm", ReportRule.REPORT_RAW, "Enable Sms Alarm"), RESCUE_SERVICE_KEY( "rescueService", ReportRule.NO_REPORT, "Rescue service name used"), // Not used after version code 19 ORGANIZATION_KEY("organization", ReportRule.REPORT_ANONYMIZE, "Organization name used"), HAS_CALLED_KEY( "hasCalled", ReportRule.NO_REPORT, "Has called"), END_USER_LICENSE_AGREED("userLicenseAgreed", ReportRule.NO_REPORT, "End user license agreed"), VERSION_CODE("versionCode", ReportRule.NO_REPORT, "Version code"), USE_FLASH_NOTIFICATION( "useFlashNotification", ReportRule.REPORT_RAW, "Use flash notification"), PRIMARY_ALARM_SIGNAL_KEY("primaryAlarmSignalKey", ReportRule.NO_REPORT, "Primary alarm signal"), SECONDARY_ALARM_SIGNAL_KEY( "secondaryAlarmSignalKey", ReportRule.NO_REPORT, "Secondary alarm signal"), USER_ADDED_ALARM_SIGNALS_KEY( "userAddedAlarmSignalsKey", ReportRule.REPORT_ANONYMIZE, "User added alarm signals used"), PRIMARY_ALARM_VIBRATION_KEY( "primaryAlarmVibrationKey", ReportRule.NO_REPORT, "Primary alarm vibration"), SECONDARY_ALARM_VIBRATION_KEY( "secondaryAlarmVibrationKey", ReportRule.NO_REPORT, "Secondary alarm vibration"), ENABLE_SMS_DEBUG_LOGGING( "enableSmsDebugLogging", ReportRule.REPORT_RAW, "Enable SMS debugging"), PRIMARY_LISTEN_REGULAR_EXPRESSIONS_KEY( "primaryListenRegularExpressionsKey", ReportRule.REPORT_ANONYMIZE, "Primary alarm triggering regular expressions used"), SECONDARY_LISTEN_REGULAR_EXPRESSIONS_KEY( "secondaryListenRegularExpressionsKey", ReportRule.REPORT_ANONYMIZE, "Secondary alarm triggering regular expressions used"), SHOW_SPLASH_SCREEN_KEY( "showSplashScreenKey", ReportRule.REPORT_RAW, "Show splash screen"), UNDEFINED_KEY( "undefinedKey", ReportRule.NO_REPORT, "Undefined setting"); // @formatter:on // The actual key to which data will be stored and fetched from private final String key; // Text of key written in a more readable way used only when data is sent to Google Analytics private final String reportText; // Rule which decide how the shared preference associated to this PrefKey should be reported to Google Analytics, if it should be reported private final ReportRule reportRule; /** * Creates a new {@link PrefKey}. * * @param key * Value(key) to associate with the PrefKey enumeration. This key is the actual key that data will be stored to or fetched from * {@link SharedPreferences} with. * @param reportRule * Rule which decide how the shared preference associated to this PrefKey should be reported to Google Analytics, if it should be * reported. * @param reportText * Text of shared preference used when PrefKey is reported to Google Analytics. */ private PrefKey(String key, ReportRule reportRule, String reportText) { this.key = key; this.reportRule = reportRule; this.reportText = reportText; } /** * To get the actual <code>Key</code> with which data is stored to and fetched from {@link SharedPreferences} with. * * @return The <code>Key</code>. */ public String getKey() { return key; } /** * To get the text of this {@link PrefKey} which should be sent of to Google Analytics. * * @return The <code>reportText</code>. */ public String getReportText() { return reportText; } /** * To get the {@link ReportRule} associated with the {@link SharedPreferences} stored with this {@link PrefKey}'s key. * * @return The <code>ReportRule</code>. */ public ReportRule getReportRule() { return reportRule; } /** * To resolve a {@link PrefKey} from given key as {@link String}. * * @param key * Key from which a <code>PrefKey</code> will be resolved from. * @return Resolved <code>PrefKey</code>, if no <code>PrefKey</code> can be resolved then {@link PrefKey#UNDEFINED_KEY} will be returned. */ public static PrefKey of(String key) { PrefKey resolvedPrefKey = UNDEFINED_KEY; for (PrefKey prefKey : PrefKey.values()) { if (prefKey.getKey().equals(key)) { resolvedPrefKey = prefKey; } } return resolvedPrefKey; } } // Singleton instance of this class, eagerly initialized private static SharedPreferencesHandler INSTANCE = new SharedPreferencesHandler(); private static final String LOG_TAG = SharedPreferencesHandler.class.getSimpleName(); // Variables needed for retrieving shared preferences private SharedPreferences sharedPref; private Editor prefsEditor; // Need a listener to discover changes made in the shared preferences private final OnSharedPreferenceChangeListener listener; /** * Creates a new instance of {@link SharedPreferencesHandler}. */ private SharedPreferencesHandler() { if (INSTANCE != null) { Log.e(LOG_TAG + ":SharedPreferencesHandler()", "SharedPreferencesHandler already instantiated"); } // Set up the listener only once as it should have the same behavior all the time listener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { // Report the change GoogleAnalyticsHandler.sendSettingsChangedEvent(prefs, key); } }; } /** * To get the <b><i>singleton</i></b> instance of {@link SharedPreferencesHandler}. * * @return Instance of <code>PreferencesHandler</code>. */ public static SharedPreferencesHandler getInstance() { return INSTANCE; } /** * To fetch values in {@link SharedPreferences}. It fetches different values depending on input parameters.<br> * Returns fetched value if all is fine else an {@link IllegalArgumentException} is thrown. * <p> * Example usage:<br> * <code>String s = fetchPrefs(PrefKey.SHARED_PREF, PrefKey.ACK_NUMBER_KEY, * DataType.STRING, context)</code>, this will retrieve a <code>String</code> from key <code>ACK_NUMBER_KEY</code> within given * <code>context</code>. * * @param sharedPreferences * <code>Shared Preferences</code> from which the values are fetched from. * @param sharedPreferencesKey * <code>Key</code> in which <code>Shared Preferences</code> is stored. * @param type * Which type of data that's supposed to be fetched. * @param context * Context in which <code>Shared Preferences</code> handling is done. * @return Returns an object with the fetched value. This object can be an instance of <b><i>Integer</i></b>, <b><i>String</i></b>, * <b><i>Boolean</i></b> or a <b><i>List of Strings</i></b>. * @see #fetchPrefs(PrefKey, PrefKey, DataType, Context, Object) * @see #storePrefs(PrefKey, PrefKey, Object, Context) */ public Object fetchPrefs(PrefKey sharedPreferences, PrefKey sharedPreferencesKey, DataType type, Context context) { switch (type) { case INTEGER: return fetchPrefs(sharedPreferences, sharedPreferencesKey, type, context, 0); case STRING: return fetchPrefs(sharedPreferences, sharedPreferencesKey, type, context, ""); case BOOLEAN: return fetchPrefs(sharedPreferences, sharedPreferencesKey, type, context, false); case LIST: // The default value when fetching a List will not be taken into consideration return fetchPrefs(sharedPreferences, sharedPreferencesKey, type, context, ""); default: // Throw in some crap here, an IllegalArgumentException will be thrown anyways return fetchPrefs(sharedPreferences, sharedPreferencesKey, type, context, ""); } } /** * To fetch values in {@link SharedPreferences}. It fetches different values depending on input parameters. This method also takes a default value * to be fetched from <code>Shared Preferences</code> if no value exist. <br> * Returns fetched value if all is fine else an {@link IllegalArgumentException} is thrown. * <p> * Example usage:<br> * <code>String s = fetchPrefs(PrefKey.SHARED_PREF, PrefKey.ACK_NUMBER_KEY, * DataType.STRING, context, "empty")</code>, this will retrieve a <code>String</code> from key <code>ACK_NUMBER_KEY</code> within given * <code>context</code>. * <p> * <b><i>NOTE. The default value does only work for data types INTEGER, STRING and BOOLEAN. A default value for data type LIST will not be taken * in consideration</i></b>. <br> * * @param sharedPreferences * <code>Shared Preferences</code> from which the values are fetched from. * @param sharedPreferencesKey * <code>Key</code> in which <code>Shared Preferences</code> is stored. * @param type * Which type of data that's supposed to be fetched. * @param context * Context in which <code>Shared Preferences</code> handling is done. * @param defaultObject * Default value to be fetched from <code>Shared Preferences</code> if no previous value exist. * @return Returns an object with the fetched value. This object can be an instance of <b><i>Integer</i></b>, <b><i>String</i></b>, * <b><i>Boolean</i></b> or a <b><i>List of Strings</i></b>. * @see #fetchPrefs(PrefKey, PrefKey, DataType, Context) * @see #storePrefs(PrefKey, PrefKey, Object, Context) */ public Object fetchPrefs(PrefKey sharedPreferences, PrefKey sharedPreferencesKey, DataType type, Context context, Object defaultObject) { // Set shared preferences from context sharedPref = context.getSharedPreferences(sharedPreferences.getKey(), Context.MODE_PRIVATE); switch (type) { case INTEGER: // Check that defaultObject is of correct instance else collect "hard coded" default value of 0 if (defaultObject instanceof Integer) { return sharedPref.getInt(sharedPreferencesKey.getKey(), (Integer) defaultObject); } else { return sharedPref.getInt(sharedPreferencesKey.getKey(), 0); } case STRING: // Check that defaultObject is of correct instance else collect "hard coded" default value of "" if (defaultObject instanceof String) { return sharedPref.getString(sharedPreferencesKey.getKey(), (String) defaultObject); } else { return sharedPref.getString(sharedPreferencesKey.getKey(), ""); } case BOOLEAN: // Check that defaultObject is of correct instance else collect "hard coded" default value of false if (defaultObject instanceof Boolean) { return sharedPref.getBoolean(sharedPreferencesKey.getKey(), (Boolean) defaultObject); } else { return sharedPref.getBoolean(sharedPreferencesKey.getKey(), false); } case LIST: // Retrieve JSON string String json = sharedPref.getString(sharedPreferencesKey.getKey(), ""); // List of Strings containing List<String> list = new ArrayList<String>(); // If JSON string is not empty if (json != "") { try { // Create a JSONArray from JSON string and retrieve strings from it and and them to a List<String> JSONArray a = new JSONArray(json); for (int i = 0; i < a.length(); i++) { String secondaryListenNumber = a.optString(i); list.add(secondaryListenNumber); } return list; } catch (JSONException e) { Log.e(LOG_TAG + ":fetchPrefs()", "Failed to retrieve List<String> from shared preferences: \"" + sharedPreferences.getKey() + "\", with key: \"" + sharedPreferencesKey.getKey() + "\", type: \"" + type.name() + "\" and context: \"" + context.toString() + "\"", e); } } else { // <--If JSON string is empty, return empty List return list; } break; default: // DO NOTHING, EXCEPTION WILL BE THROWN LATER! } // If application end up here then some error has occurred IllegalArgumentException exception = new IllegalArgumentException("Failed to fetch shared preferences: \"" + sharedPreferences.getKey() + "\", with key: \"" + sharedPreferencesKey.getKey() + "\", data type: \"" + type.name() + "\" and context: \"" + context.toString() + "\". Cause: \"Data type given as argument is unsupported\", valid data types are: \"INTEGER\", \"STRING\", \"BOOLEAN\" and \"LIST\""); Log.e(LOG_TAG + ":fetchPrefs()", "An exception occurred while fetching shared preferences", exception); throw exception; } /** * To store values to {@link SharedPreferences}. It set's different values of different instances depending on input parameters.<br> * Example usage: * <p> * <code>storePrefs(PrefKey.SHARED_PREF, PrefKey.ACKNUMBER_KEY, "0457 0000 000", context)</code> * * @param sharedPreference * <code>Shared Preferences</code> to which given object is stored to. * @param sharedPreferencesKey * <code>Key</code> to which <code>Shared Preference</code> the object is going to be stored to. * @param object * Object to be stored to <code>Shared Preference</code>, supported instances are <code>Integer</code>, <code>String</code>, * <code>Boolean</code> and <code>List(containing instances of String)</code>. * @param context * Context in which <code>Shared Preferences</code> handling is done. * @see #fetchPrefs(PrefKey, PrefKey, DataType, Context) * @see #fetchPrefs(PrefKey, PrefKey, DataType, Context, Object) */ @SuppressWarnings("unchecked") public void storePrefs(PrefKey sharedPreference, PrefKey sharedPreferencesKey, Object object, Context context) { // Set shared preferences from context and set listener to it sharedPref = context.getSharedPreferences(sharedPreference.getKey(), Context.MODE_PRIVATE); sharedPref.registerOnSharedPreferenceChangeListener(listener); // Resolve editor for the shared preferences prefsEditor = sharedPref.edit(); if (object instanceof Integer) { // Put shared preferences as Integer prefsEditor.putInt(sharedPreferencesKey.getKey(), (Integer) object); prefsEditor.commit(); } else if (object instanceof String) { // Put shared preferences as String prefsEditor.putString(sharedPreferencesKey.getKey(), (String) object); prefsEditor.commit(); } else if (object instanceof Boolean) { // Put shared preferences as Boolean prefsEditor.putBoolean(sharedPreferencesKey.getKey(), (Boolean) object); prefsEditor.commit(); } else if (object instanceof List<?>) { // Create a new list and store object to it List<String> list = new ArrayList<String>(); list = (List<String>) object; // Use JSON to serialize ArrayList containing strings JSONArray a = new JSONArray(); // Iterate through each element in list and add object in JSON object for (int i = 0; i < list.size(); i++) { a.put(list.get(i)); } // If list is not empty add it to shared preferences, if empty add empty string to preferences if (!list.isEmpty()) { prefsEditor.putString(sharedPreferencesKey.getKey(), a.toString()); prefsEditor.commit(); } else { prefsEditor.putString(sharedPreferencesKey.getKey(), ""); prefsEditor.commit(); } } else { // Unregister listener when shit hits the fan also sharedPref.unregisterOnSharedPreferenceChangeListener(listener); // If application end up here then some error has occurred IllegalArgumentException exception = new IllegalArgumentException( "Failed to store object to shared preferences: \"" + sharedPreference.getKey() + "\", with key: \"" + sharedPreferencesKey.getKey() + "\" and context: \"" + context.toString() + "\". Cause: \"Object of unsupported instance was given as argument\", given object is instance of: \"" + object.getClass().getSimpleName() + "\", valid instances are: \"int\", \"String\", \"boolean\" and \"List<String>\""); Log.e(LOG_TAG + ":storePrefs()", "An exception occurred while setting shared preferences", exception); throw exception; } // Remember to unregister the listener sharedPref.unregisterOnSharedPreferenceChangeListener(listener); } }