fdroid-client/app/src/main/java/org/fdroid/fdroid/Preferences.java

679 lines
26 KiB
Java

/*
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2013-2017 Peter Serwylo <peter@serwylo.com>
* Copyright (C) 2013-2016 Daniel Martí <mvdan@mvdan.cc>
* Copyright (C) 2014-2018 Hans-Christoph Steiner <hans@eds.org>
* Copyright (C) 2018 Senecto Limited
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.fdroid.fdroid;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.text.format.DateUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.installer.PrivilegedInstaller;
import org.fdroid.fdroid.net.ConnectivityMonitorService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Handles shared preferences for FDroid, looking after the names of
* preferences, default values and caching. Needs to be setup in the FDroidApp
* (using {@link Preferences#setup(android.content.Context)} before it gets
* accessed via the {@link org.fdroid.fdroid.Preferences#get()}
* singleton method.
* <p>
* All defaults should be set in {@code res/xml/preferences.xml}. The one
* exception is {@link Preferences#PREF_LOCAL_REPO_NAME} since it needs to be
* generated per install. The preferences are only written out explicitly when
* the user changes the preferences. So the default values need to be reloaded
* every time F-Droid starts. The various {@link SharedPreferences} getters are
* using {@code false} and {@code -1} as fallback default values to help catch
* problems with the proper default loading as quickly as possible.
*/
public final class Preferences implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "Preferences";
private final SharedPreferences preferences;
private Preferences(Context context) {
PreferenceManager.setDefaultValues(context, R.xml.preferences, true);
preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.registerOnSharedPreferenceChangeListener(this);
SharedPreferences.Editor editor = preferences.edit();
if (preferences.getString(PREF_LOCAL_REPO_NAME, null) == null) {
editor.putString(PREF_LOCAL_REPO_NAME, getDefaultLocalRepoName());
}
if (!preferences.contains(PREF_AUTO_DOWNLOAD_INSTALL_UPDATES)) {
editor.putBoolean(PREF_AUTO_DOWNLOAD_INSTALL_UPDATES,
PrivilegedInstaller.isExtensionInstalledCorrectly(context)
!= PrivilegedInstaller.IS_EXTENSION_INSTALLED_YES);
}
editor.apply();
}
public static final String PREF_OVER_WIFI = "overWifi";
public static final String PREF_OVER_DATA = "overData";
public static final String PREF_UPDATE_INTERVAL = "updateIntervalSeekBarPosition";
public static final String PREF_AUTO_DOWNLOAD_INSTALL_UPDATES = "updateAutoDownload";
public static final String PREF_UPDATE_NOTIFICATION_ENABLED = "updateNotify";
public static final String PREF_THEME = "theme";
public static final String PREF_USE_PURE_BLACK_DARK_THEME = "usePureBlackDarkTheme";
public static final String PREF_SHOW_INCOMPAT_VERSIONS = "incompatibleVersions";
public static final String PREF_SHOW_ANTI_FEATURES = "showAntiFeatures";
public static final String PREF_FORCE_TOUCH_APPS = "ignoreTouchscreen";
public static final String PREF_PROMPT_TO_SEND_CRASH_REPORTS = "promptToSendCrashReports";
public static final String PREF_KEEP_CACHE_TIME = "keepCacheFor";
public static final String PREF_UNSTABLE_UPDATES = "unstableUpdates";
public static final String PREF_KEEP_INSTALL_HISTORY = "keepInstallHistory";
public static final String PREF_SEND_TO_FDROID_METRICS = "sendToFdroidMetrics";
public static final String PREF_EXPERT = "expert";
public static final String PREF_FORCE_OLD_INDEX = "forceOldIndex";
public static final String PREF_PRIVILEGED_INSTALLER = "privilegedInstaller";
public static final String PREF_LOCAL_REPO_NAME = "localRepoName";
public static final String PREF_LOCAL_REPO_HTTPS = "localRepoHttps";
public static final String PREF_SCAN_REMOVABLE_STORAGE = "scanRemovableStorage";
public static final String PREF_LANGUAGE = "language";
public static final String PREF_USE_TOR = "useTor";
public static final String PREF_ENABLE_PROXY = "enableProxy";
public static final String PREF_PROXY_HOST = "proxyHost";
public static final String PREF_PROXY_PORT = "proxyPort";
public static final String PREF_SHOW_NFC_DURING_SWAP = "showNfcDuringSwap";
public static final String PREF_PREVENT_SCREENSHOTS = "preventScreenshots";
public static final String PREF_PANIC_EXIT = "pref_panic_exit";
public static final String PREF_PANIC_HIDE = "pref_panic_hide";
public static final String PREF_PANIC_RESET_REPOS = "pref_panic_reset_repos";
public static final String PREF_PANIC_WIPE_SET = "panicWipeSet";
public static final String PREF_PANIC_TMP_SELECTED_SET = "panicTmpSelectedSet";
public static final String PREF_HIDE_ON_LONG_PRESS_SEARCH = "hideOnLongPressSearch";
public static final String PREF_HIDE_ALL_NOTIFICATIONS = "hideAllNotifications";
public static final String PREF_SEND_VERSION_AND_UUID_TO_SERVERS = "sendVersionAndUUIDToServers";
public static final int OVER_NETWORK_NEVER = 0;
public static final int OVER_NETWORK_ON_DEMAND = 1;
public static final int OVER_NETWORK_ALWAYS = 2;
// not shown in Settings
private static final String PREF_LAST_UPDATE_CHECK = "lastUpdateCheck";
// these preferences are not listed in preferences.xml so the defaults are set here
@SuppressWarnings("PMD.AvoidUsingHardCodedIP")
public static final String DEFAULT_PROXY_HOST = "127.0.0.1"; // TODO move to preferences.xml
public static final int DEFAULT_PROXY_PORT = 8118; // TODO move to preferences.xml
private static final int DEFAULT_LAST_UPDATE_CHECK = -1;
private static final boolean DEFAULT_SHOW_NFC_DURING_SWAP = true;
private static final boolean DEFAULT_PANIC_EXIT = true;
private static final boolean IGNORED_B = false;
private static final int IGNORED_I = -1;
/**
* Old preference replaced by {@link #PREF_KEEP_CACHE_TIME}
*/
@Deprecated
private static final String OLD_PREF_CACHE_APK = "cacheDownloaded";
@Deprecated
private static final String OLD_PREF_UPDATE_INTERVAL = "updateInterval";
@Deprecated
private static final String OLD_PREF_UPDATE_ON_WIFI_ONLY = "updateOnWifiOnly";
public enum Theme {
light,
dark,
followSystem,
night, // Obsolete
lightWithDarkActionBar, // Obsolete
}
public static final long UPDATE_INTERVAL_DISABLED = Long.MAX_VALUE;
public static final long[] UPDATE_INTERVAL_VALUES = {
UPDATE_INTERVAL_DISABLED,
DateUtils.WEEK_IN_MILLIS * 2,
DateUtils.WEEK_IN_MILLIS,
DateUtils.DAY_IN_MILLIS,
DateUtils.HOUR_IN_MILLIS * 12,
DateUtils.HOUR_IN_MILLIS * 4,
DateUtils.HOUR_IN_MILLIS,
};
private Set<String> showAppsWithAntiFeatures;
private final Map<String, Boolean> initialized = new HashMap<>();
private final List<ChangeListener> showAppsRequiringAntiFeaturesListeners = new ArrayList<>();
private final List<ChangeListener> localRepoNameListeners = new ArrayList<>();
private final List<ChangeListener> localRepoHttpsListeners = new ArrayList<>();
private final List<ChangeListener> unstableUpdatesListeners = new ArrayList<>();
private boolean isInitialized(String key) {
return initialized.containsKey(key) && initialized.get(key);
}
private void initialize(String key) {
initialized.put(key, true);
}
private void uninitialize(String key) {
initialized.put(key, false);
}
public boolean promptToSendCrashReports() {
return preferences.getBoolean(PREF_PROMPT_TO_SEND_CRASH_REPORTS, IGNORED_B);
}
public boolean isForceOldIndexEnabled() {
return preferences.getBoolean(PREF_FORCE_OLD_INDEX, IGNORED_B);
}
/**
* Whether to use the Privileged Installer, based on if it is installed. Only the disabled
* state is stored as a preference since the enabled state is based entirely on the presence
* of the Privileged Extension. The preference provides a way to disable using the
* Privileged Extension even though its installed.
*
* @see org.fdroid.fdroid.views.PreferencesFragment#initPrivilegedInstallerPreference()
*/
public boolean isPrivilegedInstallerEnabled() {
return preferences.getBoolean(PREF_PRIVILEGED_INSTALLER, true);
}
/**
* Get the update interval in milliseconds.
*/
public long getUpdateInterval() {
int position = preferences.getInt(PREF_UPDATE_INTERVAL, IGNORED_I);
return UPDATE_INTERVAL_VALUES[position];
}
/**
* Migrate old preferences to new preferences. These need to be processed
* and committed before {@code preferences.xml} is loaded.
*/
@SuppressLint("ApplySharedPref")
public void migrateOldPreferences() {
SharedPreferences.Editor editor = preferences.edit();
if (migrateUpdateIntervalStringToInt(editor) || migrateOnlyOnWifi(editor)) {
editor.commit();
}
}
/**
* The original preference was a {@link String}, now it must be a {@link Integer}
* since {@link androidx.preference.SeekBarPreference} uses it
* directly.
*/
private boolean migrateUpdateIntervalStringToInt(SharedPreferences.Editor editor) {
if (!preferences.contains(OLD_PREF_UPDATE_INTERVAL)) {
return false; // already completed
}
int updateInterval = 3;
String value = preferences.getString(OLD_PREF_UPDATE_INTERVAL, String.valueOf(24));
if ("1".equals(value)) { // 1 hour
updateInterval = 6;
} else if ("4".equals(value)) { // 4 hours
updateInterval = 5;
} else if ("12".equals(value)) { // 12 hours
updateInterval = 4;
} else if ("24".equals(value)) { // 1 day
updateInterval = 3;
} else if ("168".equals(value)) { // 2 weeks
updateInterval = 2;
} else if ("336".equals(value)) { // 1 week
updateInterval = 1;
} else if ("0".equals(value)) { // never
updateInterval = 0;
}
editor
.putInt(PREF_UPDATE_INTERVAL, updateInterval)
.remove(OLD_PREF_UPDATE_INTERVAL);
return true;
}
/**
* The original preference was just a "Only on Wifi" checkbox.
*/
private boolean migrateOnlyOnWifi(SharedPreferences.Editor editor) {
if (!preferences.contains(OLD_PREF_UPDATE_ON_WIFI_ONLY)) {
return false; // already completed
}
int wifi;
int data;
if (preferences.getBoolean(OLD_PREF_UPDATE_ON_WIFI_ONLY, true)) {
wifi = OVER_NETWORK_ALWAYS;
data = OVER_NETWORK_NEVER;
} else {
wifi = OVER_NETWORK_ALWAYS;
data = OVER_NETWORK_ON_DEMAND;
}
editor
.putInt(PREF_OVER_WIFI, wifi)
.putInt(PREF_OVER_DATA, data)
.remove(OLD_PREF_UPDATE_ON_WIFI_ONLY);
return true;
}
/**
* Time in millis to keep cached files. Anything that has been around longer will be deleted
*/
public long getKeepCacheTime() {
String value = preferences.getString(PREF_KEEP_CACHE_TIME, null);
long fallbackValue = TimeUnit.DAYS.toMillis(1);
// the first time this was migrated, it was botched, so reset to default
switch (value) {
case "3600":
case "86400":
case "604800":
case "2592000":
case "31449600":
case "2147483647":
SharedPreferences.Editor editor = preferences.edit();
editor.remove(PREF_KEEP_CACHE_TIME);
editor.apply();
return fallbackValue;
}
if (preferences.contains(OLD_PREF_CACHE_APK)) {
if (preferences.getBoolean(OLD_PREF_CACHE_APK, false)) {
value = String.valueOf(Long.MAX_VALUE);
}
SharedPreferences.Editor editor = preferences.edit();
editor.remove(OLD_PREF_CACHE_APK);
editor.putString(PREF_KEEP_CACHE_TIME, value);
editor.apply();
}
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return fallbackValue;
}
}
long getLastUpdateCheck() {
return preferences.getLong(PREF_LAST_UPDATE_CHECK, DEFAULT_LAST_UPDATE_CHECK);
}
void setLastUpdateCheck(long lastUpdateCheck) {
preferences.edit().putLong(PREF_LAST_UPDATE_CHECK, lastUpdateCheck).apply();
}
/**
* The first time the app has been run since fresh install or clearing all data.
*/
public boolean isIndexNeverUpdated() {
return getLastUpdateCheck() == DEFAULT_LAST_UPDATE_CHECK;
}
public boolean getUnstableUpdates() {
return preferences.getBoolean(PREF_UNSTABLE_UPDATES, IGNORED_B);
}
public String getReleaseChannel() {
if (getUnstableUpdates()) return Apk.RELEASE_CHANNEL_BETA;
else return Apk.RELEASE_CHANNEL_STABLE;
}
/**
* In the backend, stable/production release channel is the default, so it expects null or empty list.
*/
@Nullable
public List<String> getBackendReleaseChannels() {
if (getUnstableUpdates()) return Collections.singletonList(Apk.RELEASE_CHANNEL_BETA);
else return null;
}
public void setUnstableUpdates(boolean value) {
preferences.edit().putBoolean(PREF_UNSTABLE_UPDATES, value).apply();
}
public boolean isKeepingInstallHistory() {
return preferences.getBoolean(PREF_KEEP_INSTALL_HISTORY, IGNORED_B);
}
public boolean isSendingToFDroidMetrics() {
return isKeepingInstallHistory() && preferences.getBoolean(PREF_SEND_TO_FDROID_METRICS, IGNORED_B);
}
public boolean showIncompatibleVersions() {
return preferences.getBoolean(PREF_SHOW_INCOMPAT_VERSIONS, IGNORED_B);
}
public boolean showNfcDuringSwap() {
return preferences.getBoolean(PREF_SHOW_NFC_DURING_SWAP, DEFAULT_SHOW_NFC_DURING_SWAP);
}
public void setShowNfcDuringSwap(boolean show) {
preferences.edit().putBoolean(PREF_SHOW_NFC_DURING_SWAP, show).apply();
}
public boolean expertMode() {
return preferences.getBoolean(PREF_EXPERT, IGNORED_B);
}
public void setExpertMode(boolean flag) {
preferences.edit().putBoolean(PREF_EXPERT, flag).apply();
}
public boolean forceTouchApps() {
return preferences.getBoolean(Preferences.PREF_FORCE_TOUCH_APPS, IGNORED_B);
}
public Theme getTheme() {
return Theme.valueOf(preferences.getString(Preferences.PREF_THEME, null));
}
public boolean isPureBlack() {
return preferences.getBoolean(Preferences.PREF_USE_PURE_BLACK_DARK_THEME, false);
}
public boolean isLocalRepoHttpsEnabled() {
return false; // disabled until it works well
}
private String getDefaultLocalRepoName() {
return (Build.BRAND + " " + Build.MODEL + new Random().nextInt(9999))
.replaceAll(" ", "-");
}
public String getLanguage() {
return preferences.getString(Preferences.PREF_LANGUAGE, Languages.USE_SYSTEM_DEFAULT);
}
public void clearLanguage() {
preferences.edit().remove(Preferences.PREF_LANGUAGE).apply();
}
public String getLocalRepoName() {
return preferences.getString(PREF_LOCAL_REPO_NAME, getDefaultLocalRepoName());
}
public boolean isScanRemovableStorageEnabled() {
return preferences.getBoolean(PREF_SCAN_REMOVABLE_STORAGE, true);
}
public boolean isUpdateNotificationEnabled() {
return preferences.getBoolean(PREF_UPDATE_NOTIFICATION_ENABLED, true);
}
public boolean isAutoDownloadEnabled() {
return preferences.getBoolean(PREF_AUTO_DOWNLOAD_INSTALL_UPDATES, IGNORED_B);
}
/**
* Do the network conditions and user preferences allow for things to be
* downloaded in the background.
*/
public boolean isBackgroundDownloadAllowed() {
if (FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_NO_LIMIT) {
return getOverWifi() == OVER_NETWORK_ALWAYS;
} else if (FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_METERED) {
return getOverData() == OVER_NETWORK_ALWAYS;
}
return false;
}
public boolean isOnDemandDownloadAllowed() {
if (FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_NO_LIMIT
|| FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_DEVICE_AP_WITHOUT_INTERNET) {
return getOverWifi() != OVER_NETWORK_NEVER;
} else if (FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_METERED) {
return getOverData() != OVER_NETWORK_NEVER;
}
return false;
}
public int getOverWifi() {
return preferences.getInt(PREF_OVER_WIFI, IGNORED_I);
}
public int getOverData() {
return preferences.getInt(PREF_OVER_DATA, IGNORED_I);
}
/**
* Some users never use WiFi, this lets us check for that state on first run.
*/
public void setDefaultForDataOnlyConnection(Context context) {
ConnectivityManager cm = ContextCompat.getSystemService(context, ConnectivityManager.class);
if (cm == null) {
return;
}
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork == null || !activeNetwork.isConnectedOrConnecting()) {
return;
}
if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
NetworkInfo wifiNetwork = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (!wifiNetwork.isConnectedOrConnecting()) {
preferences.edit().putInt(PREF_OVER_DATA, OVER_NETWORK_ALWAYS).apply();
}
}
}
/**
* This preference's default is set dynamically based on whether Orbot is
* installed. If Orbot is installed, default to using Tor, the user can still override
*/
public boolean isTorEnabled() {
// TODO enable once Orbot can auto-start after first install
//return preferences.getBoolean(PREF_USE_TOR, OrbotHelper.requestStartTor(context));
return preferences.getBoolean(PREF_USE_TOR, IGNORED_B);
}
public boolean isProxyEnabled() {
return preferences.getBoolean(PREF_ENABLE_PROXY, IGNORED_B);
}
public String getProxyHost() {
return preferences.getString(PREF_PROXY_HOST, DEFAULT_PROXY_HOST);
}
public int getProxyPort() {
final String port = preferences.getString(PREF_PROXY_PORT, String.valueOf(DEFAULT_PROXY_PORT));
try {
return Math.min(Integer.parseInt(port), 65535);
} catch (NumberFormatException e) {
// hack until this can be a number-only preference
try {
return Math.min(Integer.parseInt(port.replaceAll("[^0-9]", "")), 65535);
} catch (Exception e1) {
return DEFAULT_PROXY_PORT;
}
}
}
public boolean preventScreenshots() {
return preferences.getBoolean(PREF_PREVENT_SCREENSHOTS, IGNORED_B);
}
public boolean panicExit() {
return preferences.getBoolean(PREF_PANIC_EXIT, DEFAULT_PANIC_EXIT);
}
public boolean panicHide() {
return preferences.getBoolean(PREF_PANIC_HIDE, IGNORED_B);
}
public boolean panicResetRepos() {
return preferences.getBoolean(PREF_PANIC_RESET_REPOS, IGNORED_B);
}
public boolean hideOnLongPressSearch() {
return preferences.getBoolean(PREF_HIDE_ON_LONG_PRESS_SEARCH, IGNORED_B);
}
public Set<String> getPanicTmpSelectedSet() {
return preferences.getStringSet(Preferences.PREF_PANIC_TMP_SELECTED_SET, Collections.<String>emptySet());
}
public void setPanicTmpSelectedSet(Set<String> selectedSet) {
preferences.edit().putStringSet(Preferences.PREF_PANIC_TMP_SELECTED_SET, selectedSet).apply();
}
public Set<String> getPanicWipeSet() {
return preferences.getStringSet(Preferences.PREF_PANIC_WIPE_SET, Collections.<String>emptySet());
}
public void setPanicWipeSet(Set<String> selectedSet) {
preferences.edit().putStringSet(Preferences.PREF_PANIC_WIPE_SET, selectedSet).apply();
}
/**
* Preference for whitelabel builds that are meant to be entirely controlled
* by the server, without user interaction, e.g. "appliances".
*/
public boolean hideAllNotifications() {
return preferences.getBoolean(PREF_HIDE_ALL_NOTIFICATIONS, IGNORED_B);
}
/**
* Whether to include the version of this app and a randomly generated ID
* to the server when downloading from it.
*/
public boolean sendVersionAndUUIDToServers() {
return preferences.getBoolean(PREF_SEND_VERSION_AND_UUID_TO_SERVERS, IGNORED_B);
}
/**
* This is cached as it is called several times inside app list adapters.
* Providing it here means the shared preferences file only needs to be
* read once, and we will keep our copy up to date by listening to changes
* in PREF_SHOW_ANTI_FEATURES.
*/
public Set<String> showAppsWithAntiFeatures() {
if (!isInitialized(PREF_SHOW_ANTI_FEATURES)) {
initialize(PREF_SHOW_ANTI_FEATURES);
showAppsWithAntiFeatures = preferences.getStringSet(
PREF_SHOW_ANTI_FEATURES, null);
}
return showAppsWithAntiFeatures;
}
public void registerAppsRequiringAntiFeaturesChangeListener(ChangeListener listener) {
showAppsRequiringAntiFeaturesListeners.add(listener);
}
public void unregisterAppsRequiringAntiFeaturesChangeListener(ChangeListener listener) {
showAppsRequiringAntiFeaturesListeners.remove(listener);
}
void registerUnstableUpdatesChangeListener(ChangeListener listener) {
unstableUpdatesListeners.add(listener);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Utils.debugLog(TAG, "Invalidating preference '" + key + "'.");
uninitialize(key);
switch (key) {
case PREF_SHOW_ANTI_FEATURES:
for (ChangeListener listener : showAppsRequiringAntiFeaturesListeners) {
listener.onPreferenceChange();
}
break;
case PREF_LOCAL_REPO_NAME:
for (ChangeListener listener : localRepoNameListeners) {
listener.onPreferenceChange();
}
break;
case PREF_LOCAL_REPO_HTTPS:
for (ChangeListener listener : localRepoHttpsListeners) {
listener.onPreferenceChange();
}
break;
case PREF_UNSTABLE_UPDATES:
for (ChangeListener listener : unstableUpdatesListeners) {
listener.onPreferenceChange();
}
break;
}
}
public void registerLocalRepoHttpsListeners(ChangeListener listener) {
localRepoHttpsListeners.add(listener);
}
public void unregisterLocalRepoHttpsListeners(ChangeListener listener) {
localRepoHttpsListeners.remove(listener);
}
public interface ChangeListener {
void onPreferenceChange();
}
private static Preferences instance;
/**
* Should only be used for unit testing, whereby separate tests are required to invoke `setup()`.
* The reason we don't instead ask for the singleton to be lazily loaded in the {@link Preferences#get()}
* method is because that would require each call to that method to require a {@link Context}.
* While it is likely that most places asking for preferences have access to a {@link Context},
* it is a minor convenience to be able to ask for preferences without.
*/
public static void setupForTests(Context context) {
instance = null;
setup(context);
}
/**
* Needs to be setup before anything else tries to access it.
*/
public static void setup(Context context) {
if (instance != null) {
final String error = "Attempted to reinitialize preferences after it " +
"has already been initialized in FDroidApp";
Log.e(TAG, error);
throw new RuntimeException(error);
}
instance = new Preferences(context);
}
public static Preferences get() {
if (instance == null) {
final String error = "Attempted to access preferences before it " +
"has been initialized in FDroidApp";
Log.e(TAG, error);
throw new RuntimeException(error);
}
return instance;
}
}