org.opendatakit.properties.PropertiesSingleton.java Source code

Java tutorial

Introduction

Here is the source code for org.opendatakit.properties.PropertiesSingleton.java

Source

/*
 * Copyright (C) 2013-2014 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package org.opendatakit.properties;

import android.content.Context;
import org.apache.commons.lang3.CharEncoding;
import org.opendatakit.aggregate.odktables.rest.TableConstants;
import org.opendatakit.androidlibrary.R;
import org.opendatakit.consts.IntentConsts;
import org.opendatakit.logging.WebLogger;
import org.opendatakit.utilities.ODKFileUtils;

import java.io.*;
import java.util.*;

/**
 * Properties are in 3 classes:
 * <p>
 * (1) general (syncable) -- the contents of config/assets/app.properties
 * (2) device -- the contents of data/device.properties
 * (3) secure -- stored in ODK Services' (private) dataDir under mAppName
 * <p>
 * The tools provide the different sets of these and default values for these settings.
 * What goes into secure and device should be predefined in CommonToolProperties
 * <p>
 * If the general (syncable) file contains values for the device and secure settings,
 * these become the default settings for those values. There is also a
 * default.device.properties
 * <p>
 * Device settings and secure settings are not overwritten by changes in the
 * general (syncable) settings. You need to Reset the device configuration to
 * re-initialize these.
 */
public class PropertiesSingleton {

    private static final String TAG = PropertiesSingleton.class.getSimpleName();

    private static final String PROPERTIES_REVISION_FILENAME = "properties.revision";
    private static final String GENERAL_PROPERTIES_FILENAME = "app.properties";
    private static final String DEFAULT_DEVICE_PROPERTIES_FILENAME = "default.device.properties";
    private static final String DEVICE_PROPERTIES_FILENAME = "device.properties";
    private static final String SECURE_PROPERTIES_FILENAME = "secure.properties";
    private static final String SECURE_INSTALLATION_ID_FILENAME_PREFIX = "secure.id.";
    private static final String TOOL_INITIALIZATION_SUFFIX = ".tool_last_initialization_start_time";
    /**
     * These three are used in services.utilities.ODKServicePropertyUtils
     */
    @SuppressWarnings("WeakerAccess")
    public final String CREDENTIAL_TYPE_NONE;
    @SuppressWarnings("WeakerAccess")
    public final String CREDENTIAL_TYPE_USERNAME_PASSWORD;
    @SuppressWarnings("WeakerAccess")
    public final String CREDENTIAL_TYPE_GOOGLE_ACCOUNT;

    private final String mAppName;
    private final boolean mHasSecureStorage;
    private final File mSecureStorageDir;
    private final TreeMap<String, String> mGeneralDefaults;
    private final TreeMap<String, String> mDeviceDefaults;
    private final TreeMap<String, String> mSecureDefaults;
    private final Properties mGeneralProps;
    private final Properties mGlobalDeviceProps;
    private final Properties mDeviceProps;
    private final Properties mSecureProps;
    private int currentRevision = invalidRevision();
    private String mInstallationId;

    PropertiesSingleton(Context context, String appName, TreeMap<String, String> plainDefaults,
            TreeMap<String, String> deviceDefaults, TreeMap<String, String> secureDefaults) {
        mAppName = appName;
        mHasSecureStorage = context.getPackageName().equals(IntentConsts.AppProperties.APPLICATION_NAME);
        if (mHasSecureStorage) {
            mSecureStorageDir = context.getDir(mAppName, 0);
        } else {
            mSecureStorageDir = null;
        }
        // these strings are set as untranslateable
        CREDENTIAL_TYPE_NONE = context.getString(R.string.credential_type_none);
        CREDENTIAL_TYPE_USERNAME_PASSWORD = context.getString(R.string.credential_type_username_password);
        CREDENTIAL_TYPE_GOOGLE_ACCOUNT = context.getString(R.string.credential_type_google_account);

        mGeneralDefaults = plainDefaults;
        mDeviceDefaults = deviceDefaults;
        mSecureDefaults = secureDefaults;

        // initialize the cache of properties read from the sdcard
        mGeneralProps = new Properties();
        mGlobalDeviceProps = new Properties();
        mDeviceProps = new Properties();
        mSecureProps = new Properties();

        // call init
        init();
    }

    private static int invalidRevision() {
        return -1;
    }

    private static String toolInitializationPropertyName(String toolName) {
        return toolName + TOOL_INITIALIZATION_SUFFIX;
    }

    private static boolean isToolInitializationPropertyName(String toolName) {
        return toolName.endsWith(TOOL_INITIALIZATION_SUFFIX);
    }

    /**
     * Used in CommonToolProperties and survey's SplashScreenActivity.
     *
     * @param toolName the name of an app
     * @return the name of a property associated with the version of that app
     */
    @SuppressWarnings("WeakerAccess")
    public static String toolVersionPropertyName(String toolName) {
        return toolName + ".tool_version_code";
    }

    /**
     * Used in CommonToolProperties and survey's SplashScreenActivity.
     *
     * @param toolName the name of an app
     * @return the name of a property associated with whether the app has run before or not
     */
    @SuppressWarnings("WeakerAccess")
    public static String toolFirstRunPropertyName(String toolName) {
        return toolName + ".tool_first_run";
    }

    public String getAppName() {
        return mAppName;
    }

    private boolean isSecureProperty(String propertyName) {
        return mSecureDefaults.containsKey(propertyName);
    }

    private boolean isDeviceProperty(String propertyName) {
        return mDeviceDefaults.containsKey(propertyName);
    }

    /**
     * Hard to tell where this is used, it's used in at least DeviceSettingsFragment,
     * AdminConfigurableDeviceSettingsFragment, TablesSettingsFragment and ServerSettingsFragment
     *
     * @param propertyName the name that may or may not exist in the props object
     * @return whether the key exists
     */
    @SuppressWarnings("unused")
    public boolean containsKey(String propertyName) {
        readPropertiesIfModified();
        if (isSecureProperty(propertyName)) {
            if (propertyName.equals(CommonToolProperties.KEY_INSTALLATION_ID)) {
                return (mInstallationId != null);
            }
            return mSecureProps.containsKey(propertyName);
        } else if (isDeviceProperty(propertyName)) {
            return mDeviceProps.containsKey(propertyName);
        } else {
            return mGeneralProps.containsKey(propertyName);
        }
    }

    /**
     * Accesses the given propertyName.
     *
     * @param propertyName the key to get
     * @return null or the string value
     */
    public String getProperty(String propertyName) {
        readPropertiesIfModified();
        if (isSecureProperty(propertyName)) {
            if (!mHasSecureStorage) {
                throw new IllegalStateException(
                        "Attempt to retrieve secured property " + propertyName + " outside of ODK Services");
            }
            if (propertyName.equals(CommonToolProperties.KEY_INSTALLATION_ID)) {
                return mInstallationId;
            }
            return mSecureProps.getProperty(propertyName);
        } else if (isDeviceProperty(propertyName)) {
            return mDeviceProps.getProperty(propertyName);
        } else {
            return mGeneralProps.getProperty(propertyName);
        }
    }

    /**
     * Accesses the given propertyName.
     * <p>
     * If the value is not specified, null or an empty string, a null value is
     * returned. Boolean.TRUE is returned if the value is "true", otherwise
     * Boolean.FALSE is returned.
     * <p>
     * Used in mostly the same places as containsKey
     *
     * @param propertyName the key to get
     * @return null or boolean true/false
     */
    @SuppressWarnings("unused")
    public Boolean getBooleanProperty(String propertyName) {
        String value = getProperty(propertyName);
        if (value == null || value.isEmpty()) {
            return null;
        }
        return "true".equalsIgnoreCase(value);
    }

    /**
     * Accesses the given propertyName.
     * <p>
     * If the value is not specified, null or an empty string, or if the value
     * cannot be parsed as an integer, then null is return. Otherwise, the integer
     * value is returned.
     * <p>
     * Used in CommonToolProperties, TableUtil and
     * services.preferences.fragments.PasswordDialogFragment
     *
     * @param propertyName the key to get
     * @return an Integer with the value for the requested key or null
     */
    @SuppressWarnings("WeakerAccess")
    public Integer getIntegerProperty(String propertyName) {
        String value = getProperty(propertyName);
        if (value == null) {
            return null;
        }
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    public void setProperties(Map<String, String> properties) {
        readPropertiesIfModified();
        if (!mHasSecureStorage) {
            for (String propertyName : properties.keySet()) {
                if (isSecureProperty(propertyName)) {
                    throw new IllegalStateException(
                            "Attempted to set or clear a secure property outside " + "ODK Services");
                }
            }
        }

        boolean updatedSecureProps = false;
        boolean updatedDeviceProps = false;
        boolean updatedGeneralProps = false;

        for (String propertyName : properties.keySet()) {
            if (propertyName.equals(CommonToolProperties.KEY_INSTALLATION_ID)) {
                throw new IllegalStateException("Attempted to set or clear the installation id property");
            }
            String value = properties.get(propertyName);
            if (value == null) {
                // remove from map
                if (isSecureProperty(propertyName)) {
                    mSecureProps.remove(propertyName);
                    updatedSecureProps = true;
                } else if (isDeviceProperty(propertyName)) {
                    mDeviceProps.remove(propertyName);
                    updatedDeviceProps = true;
                } else {
                    mGeneralProps.remove(propertyName);
                    updatedGeneralProps = true;
                }
            } else {
                // set into map
                if (isSecureProperty(propertyName)) {
                    mSecureProps.setProperty(propertyName, value);
                    updatedSecureProps = true;
                } else if (isDeviceProperty(propertyName)) {
                    mDeviceProps.setProperty(propertyName, value);
                    updatedDeviceProps = true;
                } else {
                    mGeneralProps.setProperty(propertyName, value);
                    updatedGeneralProps = true;
                }
            }
        }
        writeProperties(updatedSecureProps, updatedDeviceProps, updatedGeneralProps);
    }

    /**
     * Determine whether or not the initialization task for the given toolName
     * should be run.
     * <p>
     * Used in survey's MainMenuActivity, scan's MainActivity, androidcommon's CommonActivity and
     * tables' MainActivity
     *
     * @param toolName (e.g., survey, tables, scan, etc.)
     */
    @SuppressWarnings("unused")
    public boolean shouldRunInitializationTask(String toolName) {
        // this is stored in the device properties
        init();
        String value = mDeviceProps.getProperty(toolInitializationPropertyName(toolName));
        return value == null || value.isEmpty();
    }

    /**
     * Indicate that the initialization task for this given tool has been run.
     * Used mostly in the same places as shouldRunInitializationTask
     *
     * @param toolName (e.g., survey, tables, scan, etc.)
     */
    @SuppressWarnings("unused")
    public void clearRunInitializationTask(String toolName) {
        // this is stored in the device properties
        readPropertiesIfModified();
        mDeviceProps.setProperty(toolInitializationPropertyName(toolName),
                TableConstants.nanoSecondsFromMillis(System.currentTimeMillis()));
        writeProperties(false, true, false);
    }

    /**
     * Indicate that all initialization tasks for all tools should be run (again).
     * Used in services.sync.service.SyncExecutionContext
     */
    @SuppressWarnings("unused")
    public void setAllRunInitializationTasks() {
        // this is stored in the device properties
        readPropertiesIfModified();
        ArrayList<String> keysToRemove = new ArrayList<>();

        for (Object okey : mDeviceProps.keySet()) {
            String theKey = (String) okey;
            if (isToolInitializationPropertyName(theKey)) {
                keysToRemove.add(theKey);
            }
        }
        for (String theKey : keysToRemove) {
            mDeviceProps.remove(theKey);
        }
        writeProperties(false, true, false);
    }

    /**
     * This may either be the device locale or the locale that the user has selected from the
     * common_translations list of locales (as specified on the framework settings sheet).
     * <p>
     * Used in (survey) AndroidShortcuts, MainMenuActivity, FormListLoader, (androidcommon)
     * DoActionUtils, OdkCommon, (services) OdkDatabaseServiceImpl, OdkResolveCheckpointRowLoader,
     * SyncExecutionContext, ActiveUserAndLocale, (tables) OutputUtil, MultipleChoiceSettingDialog,
     * SpreadsheetUserTable, AbsBaseWebActivity, ExportCSVActivity, ColumnListFragment,
     * EditColorRuleFragment, StatusColorRuleListFragment, ColumnPreferenceFragment,
     * TablePreferenceFragment, TableManagerFragment, ColorRuleListFragment
     *
     * @return The user's selected locale or the device's default if they didn't specify one
     */
    @SuppressWarnings("unused")
    public String getUserSelectedDefaultLocale() {
        // this is dependent upon whether the user wants to use the device locale or a
        // locale specified in the common translations file.
        String value = this.getProperty(CommonToolProperties.KEY_COMMON_TRANSLATIONS_LOCALE);
        if (value != null && !value.isEmpty() && !"_".equalsIgnoreCase(value)) {
            return value;
        } else {
            return Locale.getDefault().toString();
        }
    }

    private void init() {
        // (re)set values to defaults

        currentRevision = invalidRevision();

        mGeneralProps.clear();
        mGlobalDeviceProps.clear();
        mDeviceProps.clear();
        mSecureProps.clear();

        // populate the caches from disk...
        readProperties(true);

        boolean updatedSecureProps = false;
        boolean updatedDeviceProps = false;
        boolean updatedGeneralProps = false;

        // see if there are missing values in the general props
        // and update them from the mGeneralDefaults map.
        for (Map.Entry<String, String> entry : mGeneralDefaults.entrySet()) {
            if (!mGeneralProps.containsKey(entry.getKey())) {
                mGeneralProps.setProperty(entry.getKey(), entry.getValue());
                updatedGeneralProps = true;
            }
        }

        // scan for device properties in the (syncable) device properties file.
        // update the provided mDeviceDefaults with these new default values.
        for (Map.Entry<String, String> entry : mDeviceDefaults.entrySet()) {
            if (mGlobalDeviceProps.containsKey(entry.getKey())) {
                entry.setValue(mGlobalDeviceProps.getProperty(entry.getKey()));
            }
        }

        // see if there are missing values in the device props
        // and update them from the mDeviceDefaults map.
        for (Map.Entry<String, String> entry : mDeviceDefaults.entrySet()) {
            if (!mDeviceProps.containsKey(entry.getKey())) {
                mDeviceProps.setProperty(entry.getKey(), entry.getValue());
                updatedDeviceProps = true;
            }
        }

        // Now, scan through the secure defaults and assign them.  These will only be
        // available from within ODK Services. If we try to access them outside of that
        // application, this will be a skipped and all values return null.
        if (mHasSecureStorage) {
            for (Map.Entry<String, String> entry : mSecureDefaults.entrySet()) {
                if (entry.getKey().equals(CommonToolProperties.KEY_INSTALLATION_ID)) {
                    // special treatment
                    continue;
                }
                if (!mSecureProps.containsKey(entry.getKey())) {
                    mSecureProps.setProperty(entry.getKey(), entry.getValue());
                    updatedSecureProps = true;
                }
            }
        }

        if (updatedSecureProps || updatedDeviceProps || updatedGeneralProps) {
            writeProperties(updatedSecureProps, updatedDeviceProps, updatedGeneralProps);
        }
    }

    private void verifyDirectories() {
        try {
            ODKFileUtils.verifyExternalStorageAvailability();
            ODKFileUtils.assertDirectoryStructure(mAppName);
        } catch (Exception ignored) {
            throw new IllegalArgumentException("External storage not available");
        }
    }

    private int getCurrentRevision() {
        int noResult = 0;
        try {
            File dataFolder = new File(ODKFileUtils.getDataFolder(mAppName));
            String[] timestampNames = dataFolder.list(new FilenameFilter() {
                @Override
                public boolean accept(File file, String s) {
                    return s.startsWith(PROPERTIES_REVISION_FILENAME);
                }
            });
            if (timestampNames == null) {
                return noResult;
            }
            int bestResult = noResult;
            for (String timestampName : timestampNames) {
                String suffix = timestampName.substring(PROPERTIES_REVISION_FILENAME.length());
                if (suffix.length() <= 1) {
                    continue;
                }

                try {
                    // try to convert suffix to an int value
                    int result = Integer.valueOf(suffix.substring(1), 16);

                    // is this value greater than all others?
                    if (result > bestResult) {
                        bestResult = result;
                    }
                } catch (NumberFormatException ignored) {
                    // ignore
                }
            }
            // NOTE: If files cannot be deleted, we may pin at unchanged.
            return bestResult;
        } catch (Exception e) {
            WebLogger.getLogger(mAppName).printStackTrace(e);
        }
        return noResult;
    }

    private int incrementAndWriteRevision(int oldRevision) {
        ++oldRevision;

        String suffix = "." + Integer.toString(oldRevision, 16);
        // write that file
        FileOutputStream timestampOutputStream = null;
        try {

            File timestampFile = new File(ODKFileUtils.getDataFolder(mAppName),
                    PROPERTIES_REVISION_FILENAME + suffix);
            timestampOutputStream = new FileOutputStream(timestampFile);
            timestampOutputStream.write(oldRevision);
            timestampOutputStream.flush();
            timestampOutputStream.close();
            timestampOutputStream = null;
        } catch (Exception e) {
            WebLogger.getLogger(mAppName).printStackTrace(e);
        } finally {
            if (timestampOutputStream != null) {
                try {
                    timestampOutputStream.close();
                } catch (IOException e) {
                    // ignore
                    WebLogger.getLogger(mAppName).printStackTrace(e);
                }
            }
        }
        // remove files other than the one we wrote
        File dataFolder = new File(ODKFileUtils.getDataFolder(mAppName));
        String[] timestampNames = dataFolder.list(new FilenameFilter() {
            @Override
            public boolean accept(File file, String s) {
                return s.startsWith(PROPERTIES_REVISION_FILENAME);
            }
        });
        if (timestampNames != null) {
            for (String name : timestampNames) {
                if (!name.equals(PROPERTIES_REVISION_FILENAME + suffix)) {
                    File timestampFile = new File(dataFolder, name);
                    if (!timestampFile.delete()) {
                        throw new RuntimeException("Could not delete timestamp " + timestampFile.getName());
                    }
                }
            }
        }
        return oldRevision;
    }

    private void readPropertiesIfModified() {

        int newRevision = getCurrentRevision();
        if (newRevision != currentRevision) {
            readProperties(false);
        }

    }

    private void readProperties(boolean includingGlobalDeviceProps) {
        verifyDirectories();

        WebLogger.getLogger(mAppName).i("PropertiesSingleton",
                "readProperties(" + includingGlobalDeviceProps + ")");

        GainPropertiesLock theLock = new GainPropertiesLock(mAppName);
        try {
            // OK. Now access files...
            FileInputStream configFileInputStream = null;
            try {
                File configFile = new File(ODKFileUtils.getAssetsFolder(mAppName), GENERAL_PROPERTIES_FILENAME);

                if (configFile.exists()) {
                    configFileInputStream = new FileInputStream(configFile);

                    mGeneralProps.loadFromXML(configFileInputStream);
                }
            } catch (Exception e) {
                WebLogger.getLogger(mAppName).printStackTrace(e);
            } finally {
                if (configFileInputStream != null) {
                    try {
                        configFileInputStream.close();
                    } catch (IOException e) {
                        // ignore
                        WebLogger.getLogger(mAppName).printStackTrace(e);
                    }
                }
            }

            // read-only values
            if (includingGlobalDeviceProps) {
                configFileInputStream = null;
                try {
                    File configFile = new File(ODKFileUtils.getAssetsFolder(mAppName),
                            DEFAULT_DEVICE_PROPERTIES_FILENAME);

                    if (configFile.exists()) {
                        configFileInputStream = new FileInputStream(configFile);

                        mGlobalDeviceProps.loadFromXML(configFileInputStream);
                    }
                } catch (Exception e) {
                    WebLogger.getLogger(mAppName).printStackTrace(e);
                } finally {
                    if (configFileInputStream != null) {
                        try {
                            configFileInputStream.close();
                        } catch (IOException e) {
                            // ignore
                            WebLogger.getLogger(mAppName).printStackTrace(e);
                        }
                    }
                }
            }

            configFileInputStream = null;
            try {
                File configFile = new File(ODKFileUtils.getDataFolder(mAppName), DEVICE_PROPERTIES_FILENAME);

                if (configFile.exists()) {
                    configFileInputStream = new FileInputStream(configFile);

                    mDeviceProps.loadFromXML(configFileInputStream);
                }
            } catch (Exception e) {
                WebLogger.getLogger(mAppName).printStackTrace(e);
            } finally {
                if (configFileInputStream != null) {
                    try {
                        configFileInputStream.close();
                    } catch (IOException e) {
                        // ignore
                        WebLogger.getLogger(mAppName).printStackTrace(e);
                    }
                }
            }

            configFileInputStream = null;
            if (mHasSecureStorage) {
                try {
                    File configFile = new File(mSecureStorageDir, SECURE_PROPERTIES_FILENAME);

                    if (configFile.exists()) {
                        configFileInputStream = new FileInputStream(configFile);

                        mSecureProps.loadFromXML(configFileInputStream);
                    }
                } catch (Exception e) {
                    WebLogger.getLogger(mAppName).printStackTrace(e);
                } finally {
                    if (configFileInputStream != null) {
                        try {
                            configFileInputStream.close();
                        } catch (IOException e) {
                            // ignore
                            WebLogger.getLogger(mAppName).printStackTrace(e);
                        }
                    }
                }

                // get the installation id. This is the suffix of a file.
                // If no such file exists, create it.
                try {
                    if (mInstallationId == null) {
                        String[] names = mSecureStorageDir.list(new FilenameFilter() {
                            @Override
                            public boolean accept(File file, String s) {
                                return file.getName().startsWith(SECURE_INSTALLATION_ID_FILENAME_PREFIX);
                            }
                        });
                        if (names == null || names.length == 0) {
                            mInstallationId = UUID.randomUUID().toString();
                            File installationFile = new File(mSecureStorageDir,
                                    SECURE_INSTALLATION_ID_FILENAME_PREFIX + mInstallationId);
                            installationFile.createNewFile();
                        } else {
                            // just in case -- if some start-up paths may generate 2 or more files
                            // -- use the lexically first
                            Arrays.sort(names);
                            mInstallationId = names[0].substring(SECURE_INSTALLATION_ID_FILENAME_PREFIX.length());
                        }
                    }

                } catch (Exception e) {
                    WebLogger.getLogger(mAppName).printStackTrace(e);
                }
            }

            currentRevision = getCurrentRevision();

        } finally {
            theLock.release();
        }
    }

    private void writeProperties(boolean updatedSecureProps, boolean updatedDeviceProps,
            boolean updatedGeneralProps) {
        verifyDirectories();

        GainPropertiesLock theLock = new GainPropertiesLock(mAppName);
        try {
            currentRevision = incrementAndWriteRevision(currentRevision);

            if (updatedGeneralProps) {
                try {
                    File tempConfigFile = new File(ODKFileUtils.getAssetsFolder(mAppName),
                            GENERAL_PROPERTIES_FILENAME + ".temp");
                    FileOutputStream configFileOutputStream = new FileOutputStream(tempConfigFile, false);

                    mGeneralProps.storeToXML(configFileOutputStream, null, CharEncoding.UTF_8);
                    configFileOutputStream.close();

                    File configFile = new File(ODKFileUtils.getAssetsFolder(mAppName), GENERAL_PROPERTIES_FILENAME);

                    boolean fileSuccess = tempConfigFile.renameTo(configFile);

                    if (!fileSuccess) {
                        WebLogger.getLogger(mAppName).i(TAG, "Temporary General Config File Rename Failed!");
                    }

                } catch (Exception e) {
                    WebLogger.getLogger(mAppName).printStackTrace(e);
                }
            }

            if (updatedDeviceProps) {
                try {
                    File tempConfigFile = new File(ODKFileUtils.getDataFolder(mAppName),
                            DEVICE_PROPERTIES_FILENAME + ".temp");
                    FileOutputStream configFileOutputStream = new FileOutputStream(tempConfigFile, false);

                    mDeviceProps.storeToXML(configFileOutputStream, null, CharEncoding.UTF_8);
                    configFileOutputStream.close();

                    File configFile = new File(ODKFileUtils.getDataFolder(mAppName), DEVICE_PROPERTIES_FILENAME);

                    boolean fileSuccess = tempConfigFile.renameTo(configFile);

                    if (!fileSuccess) {
                        WebLogger.getLogger(mAppName).i(TAG, "Temporary Device Config File Rename Failed!");
                    }

                } catch (Exception e) {
                    WebLogger.getLogger(mAppName).printStackTrace(e);
                }
            }

            if (updatedSecureProps && mHasSecureStorage) {
                try {
                    File tempConfigFile = new File(mSecureStorageDir, SECURE_PROPERTIES_FILENAME + ".temp");
                    FileOutputStream configFileOutputStream = new FileOutputStream(tempConfigFile, false);

                    mSecureProps.storeToXML(configFileOutputStream, null, CharEncoding.UTF_8);
                    configFileOutputStream.close();

                    File configFile = new File(mSecureStorageDir, SECURE_PROPERTIES_FILENAME);

                    boolean fileSuccess = tempConfigFile.renameTo(configFile);

                    if (!fileSuccess) {
                        WebLogger.getLogger(mAppName).i(TAG, "Temporary Secure Config File Rename Failed!");
                    }

                } catch (Exception e) {
                    WebLogger.getLogger(mAppName).printStackTrace(e);
                }
            }
        } finally {
            theLock.release();
        }
    }

    /**
     * Used only in services.clearAppPropertiesActivity
     */
    @SuppressWarnings("unused")
    public void clearSettings() {
        try {
            GainPropertiesLock theLock = new GainPropertiesLock(mAppName);
            currentRevision = incrementAndWriteRevision(currentRevision);

            try {
                File f;
                f = new File(ODKFileUtils.getDataFolder(mAppName), DEVICE_PROPERTIES_FILENAME);
                if (f.exists()) {
                    if (!f.delete()) {
                        throw new RuntimeException("Could not delete settings file " + f.getPath());
                    }
                }

                if (mHasSecureStorage) {
                    f = new File(mSecureStorageDir, SECURE_PROPERTIES_FILENAME);
                    if (f.exists()) {
                        if (!f.delete()) {
                            throw new RuntimeException("Could not delete settings file " + f.getPath());
                        }
                    }
                }
            } finally {
                theLock.release();
            }

        } finally {
            init();
        }
    }
}