diff options
author | Bananeweizen <bananeweizen@gmx.de> | 2015-08-29 11:43:38 +0200 |
---|---|---|
committer | Bananeweizen <bananeweizen@gmx.de> | 2015-08-29 11:43:38 +0200 |
commit | 45e9235419f5a3d1232e4c557f6a85534d822d3f (patch) | |
tree | 8b0399e25c669b3c7c65eec96f1a3c82a6214c04 | |
parent | 8ad70af3f005579769b8198741d589f0a1c3230f (diff) | |
download | cgeo-45e9235419f5a3d1232e4c557f6a85534d822d3f.zip cgeo-45e9235419f5a3d1232e4c557f6a85534d822d3f.tar.gz cgeo-45e9235419f5a3d1232e4c557f6a85534d822d3f.tar.bz2 |
refactoring: more null annotations
-rw-r--r-- | main/src/cgeo/geocaching/location/DistanceParser.java | 102 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/settings/Settings.java | 2413 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/utils/CryptUtils.java | 309 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/utils/EnvironmentUtils.java | 58 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/utils/FileUtils.java | 427 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/utils/Formatter.java | 492 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/utils/HtmlUtils.java | 149 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/utils/ImageUtils.java | 976 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/utils/LogTemplateProvider.java | 551 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/utils/MapUtils.java | 580 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/utils/MatcherWrapper.java | 6 |
11 files changed, 3054 insertions, 3009 deletions
diff --git a/main/src/cgeo/geocaching/location/DistanceParser.java b/main/src/cgeo/geocaching/location/DistanceParser.java index 7cbc19f..80e82f2 100644 --- a/main/src/cgeo/geocaching/location/DistanceParser.java +++ b/main/src/cgeo/geocaching/location/DistanceParser.java @@ -1,51 +1,51 @@ -package cgeo.geocaching.location; - -import cgeo.geocaching.utils.MatcherWrapper; - -import org.apache.commons.lang3.StringUtils; - -import java.util.Locale; -import java.util.regex.Pattern; - -public final class DistanceParser { - - private static final Pattern pattern = Pattern.compile("^([0-9.,]+)[ ]*(m|km|ft|yd|mi|)?$", Pattern.CASE_INSENSITIVE); - - /** - * Parse a distance string composed by a number and an optional suffix - * (such as "1.2km"). - * - * @param distanceText - * the string to analyze - * @return the distance in kilometers - * - * @throws NumberFormatException - * if the given number is invalid - */ - public static float parseDistance(final String distanceText, final boolean metricUnit) - throws NumberFormatException { - final MatcherWrapper matcher = new MatcherWrapper(pattern, distanceText); - - if (!matcher.find()) { - throw new NumberFormatException(distanceText); - } - - final float value = Float.parseFloat(matcher.group(1).replace(',', '.')); - final String unit = matcher.group(2).toLowerCase(Locale.US); - - if (unit.equals("m") || (StringUtils.isEmpty(unit) && metricUnit)) { - return value / 1000; - } - if (unit.equals("km")) { - return value; - } - if (unit.equals("yd")) { - return value * IConversion.YARDS_TO_KILOMETER; - } - if (unit.equals("mi")) { - return value * IConversion.MILES_TO_KILOMETER; - } - return value * IConversion.FEET_TO_KILOMETER; - } - -} +package cgeo.geocaching.location;
+
+import cgeo.geocaching.utils.MatcherWrapper;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+public final class DistanceParser {
+
+ private static final Pattern pattern = Pattern.compile("^([0-9.,]+)[ ]*(m|km|ft|yd|mi|)?$", Pattern.CASE_INSENSITIVE);
+
+ /**
+ * Parse a distance string composed by a number and an optional suffix
+ * (such as "1.2km").
+ *
+ * @param distanceText
+ * the string to analyze
+ * @return the distance in kilometers
+ *
+ * @throws NumberFormatException
+ * if the given number is invalid
+ */
+ public static float parseDistance(final String distanceText, final boolean metricUnit)
+ throws NumberFormatException {
+ final MatcherWrapper matcher = new MatcherWrapper(pattern, distanceText);
+
+ if (!matcher.find()) {
+ throw new NumberFormatException(distanceText);
+ }
+
+ final float value = Float.parseFloat(matcher.group(1).replace(',', '.'));
+ final String unit = StringUtils.lowerCase(matcher.group(2), Locale.US);
+
+ if ("m".equals(unit) || (StringUtils.isEmpty(unit) && metricUnit)) {
+ return value / 1000;
+ }
+ if ("km".equals(unit)) {
+ return value;
+ }
+ if ("yd".equals(unit)) {
+ return value * IConversion.YARDS_TO_KILOMETER;
+ }
+ if ("mi".equals(unit)) {
+ return value * IConversion.MILES_TO_KILOMETER;
+ }
+ return value * IConversion.FEET_TO_KILOMETER;
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/settings/Settings.java b/main/src/cgeo/geocaching/settings/Settings.java index 6fad160..71f4ebb 100644 --- a/main/src/cgeo/geocaching/settings/Settings.java +++ b/main/src/cgeo/geocaching/settings/Settings.java @@ -1,1205 +1,1208 @@ -package cgeo.geocaching.settings; - -import cgeo.geocaching.CgeoApplication; -import cgeo.geocaching.R; -import cgeo.geocaching.apps.navi.NavigationAppFactory.NavigationAppsEnum; -import cgeo.geocaching.connector.capability.ICredentials; -import cgeo.geocaching.connector.gc.GCConnector; -import cgeo.geocaching.connector.gc.GCConstants; -import cgeo.geocaching.enumerations.CacheType; -import cgeo.geocaching.enumerations.LogTypeTrackable; -import cgeo.geocaching.list.StoredList; -import cgeo.geocaching.location.Geopoint; -import cgeo.geocaching.maps.CGeoMap.MapMode; -import cgeo.geocaching.maps.LivemapStrategy; -import cgeo.geocaching.maps.MapProviderFactory; -import cgeo.geocaching.maps.google.v1.GoogleMapProvider; -import cgeo.geocaching.maps.interfaces.GeoPointImpl; -import cgeo.geocaching.maps.interfaces.MapProvider; -import cgeo.geocaching.maps.interfaces.MapSource; -import cgeo.geocaching.maps.mapsforge.MapsforgeMapProvider; -import cgeo.geocaching.maps.mapsforge.MapsforgeMapProvider.OfflineMapSource; -import cgeo.geocaching.sensors.OrientationProvider; -import cgeo.geocaching.sensors.RotationProvider; -import cgeo.geocaching.utils.CryptUtils; -import cgeo.geocaching.utils.FileUtils; -import cgeo.geocaching.utils.FileUtils.FileSelector; -import cgeo.geocaching.utils.Log; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.os.Build; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.os.Environment; -import android.preference.PreferenceManager; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; - -/** - * General c:geo preferences/settings set by the user - */ -public class Settings { - - /** - * On opening a map, we limit the _initial_ zoom. The user can still zoom out afterwards. - */ - private static final int INITIAL_MAP_ZOOM_LIMIT = 16; - private static final char HISTORY_SEPARATOR = ','; - private static final int SHOW_WP_THRESHOLD_DEFAULT = 10; - public static final int SHOW_WP_THRESHOLD_MAX = 50; - private static final int MAP_SOURCE_DEFAULT = GoogleMapProvider.GOOGLE_MAP_ID.hashCode(); - - public static final boolean HW_ACCEL_DISABLED_BY_DEFAULT = - Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || - StringUtils.equals(Build.MODEL, "HTC One X") || // HTC One X - StringUtils.equals(Build.MODEL, "HTC One S") || // HTC One S - StringUtils.equals(Build.MODEL, "GT-I8190") || // Samsung S3 mini - StringUtils.equals(Build.MODEL, "GT-S6310L") || // Samsung Galaxy Young - StringUtils.equals(Build.MODEL, "GT-P5210") || // Samsung Galaxy Tab 3 - StringUtils.equals(Build.MODEL, "GT-S7580") || // Samsung Galaxy Trend Plus - StringUtils.equals(Build.MODEL, "GT-I9105P") || // Samsung Galaxy SII Plus - StringUtils.equals(Build.MODEL, "ST25i") || // Sony Xperia U - StringUtils.equals(Build.MODEL, "bq Aquaris 5") || // bq Aquaris 5 - StringUtils.equals(Build.MODEL, "A1-810") || // Unknown A1-810 - StringUtils.equals(Build.MODEL, "GT-I9195") || // Samsung S4 mini - StringUtils.equals(Build.MODEL, "GT-I8200N"); // Samsung S3 mini - - // twitter api keys - private final static @NonNull String TWITTER_KEY_CONSUMER_PUBLIC = CryptUtils.rot13("ESnsCvAv3kEupF1GCR3jGj"); - private final static @NonNull String TWITTER_KEY_CONSUMER_SECRET = CryptUtils.rot13("7vQWceACV9umEjJucmlpFe9FCMZSeqIqfkQ2BnhV9x"); - - private static boolean useCompass = true; - - public enum CoordInputFormatEnum { - Plain, - Deg, - Min, - Sec; - - static final int DEFAULT_INT_VALUE = Min.ordinal(); - - public static CoordInputFormatEnum fromInt(final int id) { - final CoordInputFormatEnum[] values = CoordInputFormatEnum.values(); - if (id < 0 || id >= values.length) { - return Min; - } - return values[id]; - } - } - - private static final SharedPreferences sharedPrefs = PreferenceManager - .getDefaultSharedPreferences(CgeoApplication.getInstance().getBaseContext()); - static { - migrateSettings(); - final boolean isDebug = sharedPrefs.getBoolean(getKey(R.string.pref_debug), false); - Log.setDebug(isDebug); - CgeoApplication.dumpOnOutOfMemory(isDebug); - } - - /** - * Cache the mapsource locally. If that is an offline map source, each request would potentially access the - * underlying map file, leading to delays. - */ - private static MapSource mapSource; - - protected Settings() { - throw new InstantiationError(); - } - - private static void migrateSettings() { - final int LATEST_PREFERENCES_VERSION = 2; - final int currentVersion = getInt(R.string.pref_settingsversion, 0); - - // No need to migrate if we are up to date. - if (currentVersion == LATEST_PREFERENCES_VERSION) { - return; - } - - // No need to migrate if we don't have older settings, defaults will be used instead. - final String preferencesNameV0 = "cgeo.pref"; - final SharedPreferences prefsV0 = CgeoApplication.getInstance().getSharedPreferences(preferencesNameV0, Context.MODE_PRIVATE); - if (currentVersion == 0 && prefsV0.getAll().isEmpty()) { - final Editor e = sharedPrefs.edit(); - e.putInt(getKey(R.string.pref_settingsversion), LATEST_PREFERENCES_VERSION); - e.apply(); - return; - } - - if (currentVersion < 1) { - // migrate from non standard file location and integer based boolean types - final Editor e = sharedPrefs.edit(); - - e.putString(getKey(R.string.pref_temp_twitter_token_secret), prefsV0.getString(getKey(R.string.pref_temp_twitter_token_secret), null)); - e.putString(getKey(R.string.pref_temp_twitter_token_public), prefsV0.getString(getKey(R.string.pref_temp_twitter_token_public), null)); - e.putBoolean(getKey(R.string.pref_help_shown), prefsV0.getInt(getKey(R.string.pref_help_shown), 0) != 0); - e.putFloat(getKey(R.string.pref_anylongitude), prefsV0.getFloat(getKey(R.string.pref_anylongitude), 0)); - e.putFloat(getKey(R.string.pref_anylatitude), prefsV0.getFloat(getKey(R.string.pref_anylatitude), 0)); - e.putBoolean(getKey(R.string.pref_offlinemaps), 0 != prefsV0.getInt(getKey(R.string.pref_offlinemaps), 1)); - e.putBoolean(getKey(R.string.pref_offlinewpmaps), 0 != prefsV0.getInt(getKey(R.string.pref_offlinewpmaps), 0)); - e.putString(getKey(R.string.pref_webDeviceCode), prefsV0.getString(getKey(R.string.pref_webDeviceCode), null)); - e.putString(getKey(R.string.pref_webDeviceName), prefsV0.getString(getKey(R.string.pref_webDeviceName), null)); - e.putBoolean(getKey(R.string.pref_maplive), prefsV0.getInt(getKey(R.string.pref_maplive), 1) != 0); - e.putInt(getKey(R.string.pref_mapsource), prefsV0.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT)); - e.putBoolean(getKey(R.string.pref_twitter), 0 != prefsV0.getInt(getKey(R.string.pref_twitter), 0)); - e.putBoolean(getKey(R.string.pref_showaddress), 0 != prefsV0.getInt(getKey(R.string.pref_showaddress), 1)); - e.putBoolean(getKey(R.string.pref_showcaptcha), prefsV0.getBoolean(getKey(R.string.pref_showcaptcha), false)); - e.putBoolean(getKey(R.string.pref_maptrail), prefsV0.getInt(getKey(R.string.pref_maptrail), 1) != 0); - e.putInt(getKey(R.string.pref_lastmapzoom), prefsV0.getInt(getKey(R.string.pref_lastmapzoom), 14)); - e.putBoolean(getKey(R.string.pref_livelist), 0 != prefsV0.getInt(getKey(R.string.pref_livelist), 1)); - e.putBoolean(getKey(R.string.pref_units_imperial), prefsV0.getInt(getKey(R.string.pref_units_imperial), 1) != 1); - e.putBoolean(getKey(R.string.pref_skin), prefsV0.getInt(getKey(R.string.pref_skin), 0) != 0); - e.putInt(getKey(R.string.pref_lastusedlist), prefsV0.getInt(getKey(R.string.pref_lastusedlist), StoredList.STANDARD_LIST_ID)); - e.putString(getKey(R.string.pref_cachetype), prefsV0.getString(getKey(R.string.pref_cachetype), CacheType.ALL.id)); - e.putString(getKey(R.string.pref_twitter_token_secret), prefsV0.getString(getKey(R.string.pref_twitter_token_secret), null)); - e.putString(getKey(R.string.pref_twitter_token_public), prefsV0.getString(getKey(R.string.pref_twitter_token_public), null)); - e.putInt(getKey(R.string.pref_version), prefsV0.getInt(getKey(R.string.pref_version), 0)); - e.putBoolean(getKey(R.string.pref_autoloaddesc), 0 != prefsV0.getInt(getKey(R.string.pref_autoloaddesc), 1)); - e.putBoolean(getKey(R.string.pref_ratingwanted), prefsV0.getBoolean(getKey(R.string.pref_ratingwanted), true)); - e.putBoolean(getKey(R.string.pref_friendlogswanted), prefsV0.getBoolean(getKey(R.string.pref_friendlogswanted), true)); - e.putBoolean(getKey(R.string.pref_useenglish), prefsV0.getBoolean(getKey(R.string.pref_useenglish), false)); - e.putBoolean(getKey(R.string.pref_usecompass), 0 != prefsV0.getInt(getKey(R.string.pref_usecompass), 1)); - e.putBoolean(getKey(R.string.pref_trackautovisit), prefsV0.getBoolean(getKey(R.string.pref_trackautovisit), false)); - e.putBoolean(getKey(R.string.pref_sigautoinsert), prefsV0.getBoolean(getKey(R.string.pref_sigautoinsert), false)); - e.putBoolean(getKey(R.string.pref_logimages), prefsV0.getBoolean(getKey(R.string.pref_logimages), false)); - e.putBoolean(getKey(R.string.pref_excludedisabled), 0 != prefsV0.getInt(getKey(R.string.pref_excludedisabled), 0)); - e.putBoolean(getKey(R.string.pref_excludemine), 0 != prefsV0.getInt(getKey(R.string.pref_excludemine), 0)); - e.putString(getKey(R.string.pref_mapfile), prefsV0.getString(getKey(R.string.pref_mapfile), null)); - e.putString(getKey(R.string.pref_signature), prefsV0.getString(getKey(R.string.pref_signature), null)); - e.putString(getKey(R.string.pref_pass_vote), prefsV0.getString(getKey(R.string.pref_pass_vote), null)); - e.putString(getKey(R.string.pref_password), prefsV0.getString(getKey(R.string.pref_password), null)); - e.putString(getKey(R.string.pref_username), prefsV0.getString(getKey(R.string.pref_username), null)); - e.putString(getKey(R.string.pref_memberstatus), prefsV0.getString(getKey(R.string.pref_memberstatus), "")); - e.putInt(getKey(R.string.pref_coordinputformat), prefsV0.getInt(getKey(R.string.pref_coordinputformat), CoordInputFormatEnum.DEFAULT_INT_VALUE)); - e.putBoolean(getKey(R.string.pref_log_offline), prefsV0.getBoolean(getKey(R.string.pref_log_offline), false)); - e.putBoolean(getKey(R.string.pref_choose_list), prefsV0.getBoolean(getKey(R.string.pref_choose_list), true)); - e.putBoolean(getKey(R.string.pref_loaddirectionimg), prefsV0.getBoolean(getKey(R.string.pref_loaddirectionimg), true)); - e.putString(getKey(R.string.pref_gccustomdate), prefsV0.getString(getKey(R.string.pref_gccustomdate), GCConstants.DEFAULT_GC_DATE)); - e.putInt(getKey(R.string.pref_showwaypointsthreshold), prefsV0.getInt(getKey(R.string.pref_showwaypointsthreshold), SHOW_WP_THRESHOLD_DEFAULT)); - e.putString(getKey(R.string.pref_cookiestore), prefsV0.getString(getKey(R.string.pref_cookiestore), null)); - e.putBoolean(getKey(R.string.pref_opendetailslastpage), prefsV0.getBoolean(getKey(R.string.pref_opendetailslastpage), false)); - e.putInt(getKey(R.string.pref_lastdetailspage), prefsV0.getInt(getKey(R.string.pref_lastdetailspage), 1)); - e.putInt(getKey(R.string.pref_defaultNavigationTool), prefsV0.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id)); - e.putInt(getKey(R.string.pref_defaultNavigationTool2), prefsV0.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id)); - e.putInt(getKey(R.string.pref_livemapstrategy), prefsV0.getInt(getKey(R.string.pref_livemapstrategy), LivemapStrategy.AUTO.id)); - e.putBoolean(getKey(R.string.pref_debug), prefsV0.getBoolean(getKey(R.string.pref_debug), false)); - e.putInt(getKey(R.string.pref_livemaphintshowcount), prefsV0.getInt(getKey(R.string.pref_livemaphintshowcount), 0)); - - e.putInt(getKey(R.string.pref_settingsversion), 1); // mark migrated - e.apply(); - } - - // changes for new settings dialog - if (currentVersion < 2) { - final Editor e = sharedPrefs.edit(); - - e.putBoolean(getKey(R.string.pref_units_imperial), useImperialUnits()); - - // show waypoints threshold now as a slider - int wpThreshold = getWayPointsThreshold(); - if (wpThreshold < 0) { - wpThreshold = 0; - } else if (wpThreshold > SHOW_WP_THRESHOLD_MAX) { - wpThreshold = SHOW_WP_THRESHOLD_MAX; - } - e.putInt(getKey(R.string.pref_showwaypointsthreshold), wpThreshold); - - // KEY_MAP_SOURCE must be string, because it is the key for a ListPreference now - final int ms = sharedPrefs.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT); - e.remove(getKey(R.string.pref_mapsource)); - e.putString(getKey(R.string.pref_mapsource), String.valueOf(ms)); - - // navigation tool ids must be string, because ListPreference uses strings as keys - final int dnt1 = sharedPrefs.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id); - final int dnt2 = sharedPrefs.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id); - e.remove(getKey(R.string.pref_defaultNavigationTool)); - e.remove(getKey(R.string.pref_defaultNavigationTool2)); - e.putString(getKey(R.string.pref_defaultNavigationTool), String.valueOf(dnt1)); - e.putString(getKey(R.string.pref_defaultNavigationTool2), String.valueOf(dnt2)); - - // defaults for gpx directories - e.putString(getKey(R.string.pref_gpxImportDir), getGpxImportDir()); - e.putString(getKey(R.string.pref_gpxExportDir), getGpxExportDir()); - - e.putInt(getKey(R.string.pref_settingsversion), 2); // mark migrated - e.apply(); - } - } - - private static String getKey(final int prefKeyId) { - return CgeoApplication.getInstance().getString(prefKeyId); - } - - static String getString(final int prefKeyId, final String defaultValue) { - return sharedPrefs.getString(getKey(prefKeyId), defaultValue); - } - - private static int getInt(final int prefKeyId, final int defaultValue) { - return sharedPrefs.getInt(getKey(prefKeyId), defaultValue); - } - - private static long getLong(final int prefKeyId, final long defaultValue) { - return sharedPrefs.getLong(getKey(prefKeyId), defaultValue); - } - - private static boolean getBoolean(final int prefKeyId, final boolean defaultValue) { - return sharedPrefs.getBoolean(getKey(prefKeyId), defaultValue); - } - - private static float getFloat(final int prefKeyId, final float defaultValue) { - return sharedPrefs.getFloat(getKey(prefKeyId), defaultValue); - } - - protected static void putString(final int prefKeyId, final String value) { - final SharedPreferences.Editor edit = sharedPrefs.edit(); - edit.putString(getKey(prefKeyId), value); - edit.apply(); - } - - protected static void putBoolean(final int prefKeyId, final boolean value) { - final SharedPreferences.Editor edit = sharedPrefs.edit(); - edit.putBoolean(getKey(prefKeyId), value); - edit.apply(); - } - - private static void putInt(final int prefKeyId, final int value) { - final SharedPreferences.Editor edit = sharedPrefs.edit(); - edit.putInt(getKey(prefKeyId), value); - edit.apply(); - } - - private static void putLong(final int prefKeyId, final long value) { - final SharedPreferences.Editor edit = sharedPrefs.edit(); - edit.putLong(getKey(prefKeyId), value); - edit.apply(); - } - - private static void putFloat(final int prefKeyId, final float value) { - final SharedPreferences.Editor edit = sharedPrefs.edit(); - edit.putFloat(getKey(prefKeyId), value); - edit.apply(); - } - - private static void remove(final int prefKeyId) { - final SharedPreferences.Editor edit = sharedPrefs.edit(); - edit.remove(getKey(prefKeyId)); - edit.apply(); - } - - private static boolean contains(final int prefKeyId) { - return sharedPrefs.contains(getKey(prefKeyId)); - } - - public static boolean hasGCCredentials() { - final String preUsername = getString(R.string.pref_username, null); - final String prePassword = getString(R.string.pref_password, null); - - return !StringUtils.isBlank(preUsername) && !StringUtils.isBlank(prePassword); - } - - /** - * Get login and password information of Geocaching.com. - * - * @return a pair either with (login, password) or (empty, empty) if no valid information is stored - */ - public static ImmutablePair<String, String> getGcCredentials() { - return getCredentials(GCConnector.getInstance()); - } - - /** - * Get login and password information. - * - * @return a pair either with (login, password) or (empty, empty) if no valid information is stored - */ - public static ImmutablePair<String, String> getCredentials(final @NonNull ICredentials connector) { - final String username = getString(connector.getUsernamePreferenceKey(), null); - final String password = getString(connector.getPasswordPreferenceKey(), null); - - if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { - return new ImmutablePair<>(StringUtils.EMPTY, StringUtils.EMPTY); - } - - return new ImmutablePair<>(username, password); - } - - public static String getUsername() { - return getString(R.string.pref_username, StringUtils.EMPTY); - } - - public static boolean isGCConnectorActive() { - return getBoolean(R.string.pref_connectorGCActive, true); - } - - public static boolean isECConnectorActive() { - return getBoolean(R.string.pref_connectorECActive, false); - } - - public static boolean isGCPremiumMember() { - final String memberStatus = getGCMemberStatus(); - return StringUtils.equalsIgnoreCase(memberStatus, GCConstants.MEMBER_STATUS_PREMIUM) || - StringUtils.equalsIgnoreCase(memberStatus, GCConstants.MEMBER_STATUS_CHARTER); - } - - public static String getGCMemberStatus() { - return getString(R.string.pref_memberstatus, ""); - } - - public static void setGCMemberStatus(final String memberStatus) { - if (StringUtils.isBlank(memberStatus)) { - remove(R.string.pref_memberstatus); - } - putString(R.string.pref_memberstatus, memberStatus); - } - - public static ImmutablePair<String, String> getTokenPair(final int tokenPublicPrefKey, final int tokenSecretPrefKey) { - return new ImmutablePair<>(getString(tokenPublicPrefKey, null), getString(tokenSecretPrefKey, null)); - } - - public static void setTokens(final int tokenPublicPrefKey, @Nullable final String tokenPublic, final int tokenSecretPrefKey, @Nullable final String tokenSecret) { - if (tokenPublic == null) { - remove(tokenPublicPrefKey); - } else { - putString(tokenPublicPrefKey, tokenPublic); - } - if (tokenSecret == null) { - remove(tokenSecretPrefKey); - } else { - putString(tokenSecretPrefKey, tokenSecret); - } - } - - public static boolean isOCConnectorActive(final int isActivePrefKeyId) { - return getBoolean(isActivePrefKeyId, false); - } - - public static boolean hasOCAuthorization(final int tokenPublicPrefKeyId, final int tokenSecretPrefKeyId) { - return StringUtils.isNotBlank(getString(tokenPublicPrefKeyId, "")) - && StringUtils.isNotBlank(getString(tokenSecretPrefKeyId, "")); - } - - public static boolean isGCVoteLogin() { - return getGCVoteLogin() != null; - } - - public static ImmutablePair<String, String> getGCVoteLogin() { - final String username = getString(R.string.pref_username, null); - final String password = getString(R.string.pref_pass_vote, null); - - if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { - return null; - } - - return new ImmutablePair<>(username, password); - } - - public static String getSignature() { - return getString(R.string.pref_signature, StringUtils.EMPTY); - } - - public static void setCookieStore(final String cookies) { - if (StringUtils.isBlank(cookies)) { - // erase cookies - remove(R.string.pref_cookiestore); - } - // save cookies - putString(R.string.pref_cookiestore, cookies); - } - - public static String getCookieStore() { - return getString(R.string.pref_cookiestore, null); - } - - public static void setUseGooglePlayServices(final boolean value) { - putBoolean(R.string.pref_googleplayservices, value); - } - - public static boolean useGooglePlayServices() { - // By defaut, enable play services starting from ICS. - return CgeoApplication.getInstance().isGooglePlayServicesAvailable() && - getBoolean(R.string.pref_googleplayservices, VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH); - } - - public static boolean useLowPowerMode() { - return getBoolean(R.string.pref_lowpowermode, false); - } - - /** - * @param cacheType - * The cache type used for future filtering - */ - public static void setCacheType(final CacheType cacheType) { - if (cacheType == null) { - remove(R.string.pref_cachetype); - } else { - putString(R.string.pref_cachetype, cacheType.id); - } - } - - public static int getLastList() { - return getInt(R.string.pref_lastusedlist, StoredList.STANDARD_LIST_ID); - } - - public static void saveLastList(final int listId) { - putInt(R.string.pref_lastusedlist, listId); - } - - public static void setWebNameCode(final String name, final String code) { - putString(R.string.pref_webDeviceName, name); - putString(R.string.pref_webDeviceCode, code); - } - - public static MapProvider getMapProvider() { - return getMapSource().getMapProvider(); - } - - public static String getMapFile() { - return getString(R.string.pref_mapfile, null); - } - - static void setMapFile(final String mapFile) { - putString(R.string.pref_mapfile, mapFile); - if (mapFile != null) { - setMapFileDirectory(new File(mapFile).getParent()); - } - } - - public static String getMapFileDirectory() { - final String mapDir = getString(R.string.pref_mapDirectory, null); - if (mapDir != null) { - return mapDir; - } - final String mapFile = getMapFile(); - if (mapFile != null) { - return new File(mapFile).getParent(); - } - return null; - } - - static void setMapFileDirectory(final String mapFileDirectory) { - putString(R.string.pref_mapDirectory, mapFileDirectory); - MapsforgeMapProvider.getInstance().updateOfflineMaps(); - } - - private static boolean isValidMapFile() { - return isValidMapFile(getMapFile()); - } - - public static boolean isValidMapFile(final String mapFileIn) { - return MapsforgeMapProvider.isValidMapFile(mapFileIn); - } - - public static boolean isScaleMapsforgeText() { - return getBoolean(R.string.pref_mapsforge_scale_text, true); - } - - public static CoordInputFormatEnum getCoordInputFormat() { - return CoordInputFormatEnum.fromInt(getInt(R.string.pref_coordinputformat, CoordInputFormatEnum.DEFAULT_INT_VALUE)); - } - - public static void setCoordInputFormat(final CoordInputFormatEnum format) { - putInt(R.string.pref_coordinputformat, format.ordinal()); - } - - public static boolean getLogOffline() { - return getBoolean(R.string.pref_log_offline, false); - } - - public static boolean getChooseList() { - return getBoolean(R.string.pref_choose_list, false); - } - - public static boolean getLoadDirImg() { - return !isGCPremiumMember() && getBoolean(R.string.pref_loaddirectionimg, true); - } - - public static void setGcCustomDate(final String format) { - putString(R.string.pref_gccustomdate, format); - } - - /** - * @return User selected date format on GC.com - */ - public static String getGcCustomDate() { - // We might have some users whose stored value is null, which is invalid. In this case, we use the default. - return StringUtils.defaultString(getString(R.string.pref_gccustomdate, GCConstants.DEFAULT_GC_DATE), - GCConstants.DEFAULT_GC_DATE); - } - - public static boolean isExcludeMyCaches() { - return getBoolean(R.string.pref_excludemine, false); - } - - public static boolean useEnglish() { - return getBoolean(R.string.pref_useenglish, false); - } - - public static boolean isShowAddress() { - return getBoolean(R.string.pref_showaddress, true); - } - - public static boolean isShowCaptcha() { - return !isGCPremiumMember() && getBoolean(R.string.pref_showcaptcha, false); - } - - public static boolean isExcludeDisabledCaches() { - return getBoolean(R.string.pref_excludedisabled, false); - } - - public static boolean isStoreOfflineMaps() { - return getBoolean(R.string.pref_offlinemaps, true); - } - - public static boolean isStoreOfflineWpMaps() { - return getBoolean(R.string.pref_offlinewpmaps, false); - } - - public static boolean isStoreLogImages() { - return getBoolean(R.string.pref_logimages, false); - } - - public static boolean isAutoLoadDescription() { - return getBoolean(R.string.pref_autoloaddesc, true); - } - - public static boolean isRatingWanted() { - return getBoolean(R.string.pref_ratingwanted, true); - } - - public static boolean isGeokretyConnectorActive() { - return getBoolean(R.string.pref_connectorGeokretyActive, false); - } - - public static boolean isGeokretyCacheActive() { - return getBoolean(R.string.pref_geokrety_cache, true); - } - - static boolean hasGeokretyAuthorization() { - return StringUtils.isNotBlank(getGeokretySecId()); - } - - public static String getGeokretySecId() { - return getString(R.string.pref_fakekey_geokrety_authorization, null); - } - - public static void setGeokretySecId(final String secid) { - putString(R.string.pref_fakekey_geokrety_authorization, secid); - } - - public static boolean isRegisteredForGeokretyLogging() { - return getGeokretySecId() != null; - } - - /** - * Retrieve showed popup counter for warning about logging Trackable recommend Geocode - * - * @return number of times the popup has appeared - */ - public static int getLogTrackableWithoutGeocodeShowCount() { - return getInt(R.string.pref_logtrackablewithoutgeocodeshowcount, 0); - } - - /** - * Store showed popup counter for warning about logging Trackable recommend Geocode - * - * @param showCount the count to save - */ - public static void setLogTrackableWithoutGeocodeShowCount(final int showCount) { - putInt(R.string.pref_logtrackablewithoutgeocodeshowcount, showCount); - } - - public static boolean isFriendLogsWanted() { - if (!hasGCCredentials()) { - // don't show a friends log if the user is anonymous - return false; - } - return getBoolean(R.string.pref_friendlogswanted, true); - } - - public static boolean isLiveList() { - return getBoolean(R.string.pref_livelist, true); - } - - public static boolean isTrackableAutoVisit() { - return getBoolean(R.string.pref_trackautovisit, false); - } - - public static boolean isAutoInsertSignature() { - return getBoolean(R.string.pref_sigautoinsert, false); - } - - static void setUseImperialUnits(final boolean useImperialUnits) { - putBoolean(R.string.pref_units_imperial, useImperialUnits); - } - - public static boolean useImperialUnits() { - return getBoolean(R.string.pref_units_imperial, useImperialUnitsByDefault()); - } - - private static boolean useImperialUnitsByDefault() { - final String countryCode = Locale.getDefault().getCountry(); - return "US".equals(countryCode) // USA - || "LR".equals(countryCode) // Liberia - || "MM".equals(countryCode); // Burma - } - - public static boolean isLiveMap() { - return getBoolean(R.string.pref_maplive, true); - } - - public static void setLiveMap(final boolean live) { - putBoolean(R.string.pref_maplive, live); - } - - public static boolean isMapTrail() { - return getBoolean(R.string.pref_maptrail, false); - } - - public static void setMapTrail(final boolean showTrail) { - putBoolean(R.string.pref_maptrail, showTrail); - } - - /** - * whether to show a direction line on the map - */ - public static boolean isMapDirection() { - return getBoolean(R.string.pref_map_direction, true); - } - - public static void setMapDirection(final boolean showDirection) { - putBoolean(R.string.pref_map_direction, showDirection); - } - - /** - * Get last used zoom of the internal map. Differentiate between two use cases for a map of multiple caches (e.g. - * live map) and the map of a single cache (which is often zoomed in more deep). - */ - public static int getMapZoom(final MapMode mapMode) { - if (mapMode == MapMode.SINGLE || mapMode == MapMode.COORDS) { - return getCacheZoom(); - } - return getMapZoom(); - } - - public static void setMapZoom(final MapMode mapMode, final int zoomLevel) { - if (mapMode == MapMode.SINGLE || mapMode == MapMode.COORDS) { - setCacheZoom(zoomLevel); - } - else { - setMapZoom(zoomLevel); - } - } - - /** - * @return zoom used for the (live) map - */ - private static int getMapZoom() { - return Math.max(getInt(R.string.pref_lastmapzoom, 14), INITIAL_MAP_ZOOM_LIMIT); - } - - private static void setMapZoom(final int mapZoomLevel) { - putInt(R.string.pref_lastmapzoom, mapZoomLevel); - } - - /** - * @return zoom used for the map of a single cache - */ - private static int getCacheZoom() { - return Math.max(getInt(R.string.pref_cache_zoom, 14), INITIAL_MAP_ZOOM_LIMIT); - } - - private static void setCacheZoom(final int zoomLevel) { - putInt(R.string.pref_cache_zoom, zoomLevel); - } - - public static GeoPointImpl getMapCenter() { - return getMapProvider().getMapItemFactory() - .getGeoPointBase(new Geopoint(getInt(R.string.pref_lastmaplat, 0) / 1e6, - getInt(R.string.pref_lastmaplon, 0) / 1e6)); - } - - public static void setMapCenter(final GeoPointImpl mapViewCenter) { - putInt(R.string.pref_lastmaplat, mapViewCenter.getLatitudeE6()); - putInt(R.string.pref_lastmaplon, mapViewCenter.getLongitudeE6()); - } - - @NonNull - public static synchronized MapSource getMapSource() { - if (mapSource != null) { - return mapSource; - } - final int id = getConvertedMapId(); - mapSource = MapProviderFactory.getMapSource(id); - if (mapSource != null) { - // don't use offline maps if the map file is not valid - if (!(mapSource instanceof OfflineMapSource) || isValidMapFile()) { - return mapSource; - } - } - // fallback to first available map - return MapProviderFactory.getDefaultSource(); - } - - private final static int GOOGLEMAP_BASEID = 30; - private final static int MAP = 1; - private final static int SATELLITE = 2; - - private final static int MFMAP_BASEID = 40; - private final static int MAPNIK = 1; - private final static int CYCLEMAP = 3; - private final static int OFFLINE = 4; - private static final int HISTORY_SIZE = 10; - - /** - * Convert old preference ids for maps (based on constant values) into new hash based ids. - */ - private static int getConvertedMapId() { - final int id = Integer.parseInt(getString(R.string.pref_mapsource, - String.valueOf(MAP_SOURCE_DEFAULT))); - switch (id) { - case GOOGLEMAP_BASEID + MAP: - return GoogleMapProvider.GOOGLE_MAP_ID.hashCode(); - case GOOGLEMAP_BASEID + SATELLITE: - return GoogleMapProvider.GOOGLE_SATELLITE_ID.hashCode(); - case MFMAP_BASEID + MAPNIK: - return MapsforgeMapProvider.MAPSFORGE_MAPNIK_ID.hashCode(); - case MFMAP_BASEID + CYCLEMAP: - return MapsforgeMapProvider.MAPSFORGE_CYCLEMAP_ID.hashCode(); - case MFMAP_BASEID + OFFLINE: { - final String mapFile = getMapFile(); - if (StringUtils.isNotEmpty(mapFile)) { - return mapFile.hashCode(); - } - break; - } - default: - break; - } - return id; - } - - public static synchronized void setMapSource(final MapSource newMapSource) { - putString(R.string.pref_mapsource, String.valueOf(newMapSource.getNumericalId())); - if (newMapSource instanceof OfflineMapSource) { - setMapFile(((OfflineMapSource) newMapSource).getFileName()); - } - // cache the value - mapSource = newMapSource; - } - - public static void setAnyCoordinates(final Geopoint coords) { - if (null != coords) { - putFloat(R.string.pref_anylatitude, (float) coords.getLatitude()); - putFloat(R.string.pref_anylongitude, (float) coords.getLongitude()); - } else { - remove(R.string.pref_anylatitude); - remove(R.string.pref_anylongitude); - } - } - - public static Geopoint getAnyCoordinates() { - if (contains(R.string.pref_anylatitude) && contains(R.string.pref_anylongitude)) { - final float lat = getFloat(R.string.pref_anylatitude, 0); - final float lon = getFloat(R.string.pref_anylongitude, 0); - return new Geopoint(lat, lon); - } - return null; - } - - public static boolean isUseCompass() { - return useCompass; - } - - public static void setUseCompass(final boolean value) { - useCompass = value; - } - - public static boolean isLightSkin() { - return getBoolean(R.string.pref_skin, false); - } - - @NonNull - public static String getTwitterKeyConsumerPublic() { - return TWITTER_KEY_CONSUMER_PUBLIC; - } - - @NonNull - public static String getTwitterKeyConsumerSecret() { - return TWITTER_KEY_CONSUMER_SECRET; - } - - public static String getWebDeviceCode() { - return getString(R.string.pref_webDeviceCode, null); - } - - public static boolean isRegisteredForSend2cgeo() { - return getWebDeviceCode() != null; - } - - static String getWebDeviceName() { - return getString(R.string.pref_webDeviceName, Build.MODEL); - } - - /** - * @return The cache type used for filtering or ALL if no filter is active. - * Returns never null - */ - @NonNull - public static CacheType getCacheType() { - return CacheType.getById(getString(R.string.pref_cachetype, CacheType.ALL.id)); - } - - /** - * The Threshold for the showing of child waypoints - */ - public static int getWayPointsThreshold() { - return getInt(R.string.pref_showwaypointsthreshold, SHOW_WP_THRESHOLD_DEFAULT); - } - - static void setShowWaypointsThreshold(final int threshold) { - putInt(R.string.pref_showwaypointsthreshold, threshold); - } - - public static boolean isUseTwitter() { - return getBoolean(R.string.pref_twitter, false); - } - - private static void setUseTwitter(final boolean useTwitter) { - putBoolean(R.string.pref_twitter, useTwitter); - } - - public static boolean isTwitterLoginValid() { - return !StringUtils.isBlank(getTokenPublic()) - && !StringUtils.isBlank(getTokenSecret()); - } - - public static String getTokenPublic() { - return getString(R.string.pref_twitter_token_public, null); - } - - public static String getTokenSecret() { - return getString(R.string.pref_twitter_token_secret, null); - - } - - static boolean hasTwitterAuthorization() { - return StringUtils.isNotBlank(getTokenPublic()) - && StringUtils.isNotBlank(getTokenSecret()); - } - - public static void setTwitterTokens(@Nullable final String tokenPublic, - @Nullable final String tokenSecret, final boolean enableTwitter) { - putString(R.string.pref_twitter_token_public, tokenPublic); - putString(R.string.pref_twitter_token_secret, tokenSecret); - if (tokenPublic != null) { - remove(R.string.pref_temp_twitter_token_public); - remove(R.string.pref_temp_twitter_token_secret); - } - setUseTwitter(enableTwitter); - } - - public static void setTwitterTempTokens(@Nullable final String tokenPublic, - @Nullable final String tokenSecret) { - putString(R.string.pref_temp_twitter_token_public, tokenPublic); - putString(R.string.pref_temp_twitter_token_secret, tokenSecret); - } - - public static ImmutablePair<String, String> getTempToken() { - final String tokenPublic = getString(R.string.pref_temp_twitter_token_public, null); - final String tokenSecret = getString(R.string.pref_temp_twitter_token_secret, null); - return new ImmutablePair<>(tokenPublic, tokenSecret); - } - - public static int getVersion() { - return getInt(R.string.pref_version, 0); - } - - public static void setVersion(final int version) { - putInt(R.string.pref_version, version); - } - - public static boolean isOpenLastDetailsPage() { - return getBoolean(R.string.pref_opendetailslastpage, false); - } - - public static int getLastDetailsPage() { - return getInt(R.string.pref_lastdetailspage, 1); - } - - public static void setLastDetailsPage(final int index) { - putInt(R.string.pref_lastdetailspage, index); - } - - public static int getDefaultNavigationTool() { - return Integer.parseInt(getString( - R.string.pref_defaultNavigationTool, - String.valueOf(NavigationAppsEnum.COMPASS.id))); - } - - public static int getDefaultNavigationTool2() { - return Integer.parseInt(getString( - R.string.pref_defaultNavigationTool2, - String.valueOf(NavigationAppsEnum.INTERNAL_MAP.id))); - } - - public static LivemapStrategy getLiveMapStrategy() { - return LivemapStrategy.getById(getInt(R.string.pref_livemapstrategy, LivemapStrategy.AUTO.id)); - } - - public static void setLiveMapStrategy(final LivemapStrategy strategy) { - putInt(R.string.pref_livemapstrategy, strategy.id); - } - - public static boolean isDebug() { - return Log.isDebug(); - } - - public static int getLiveMapHintShowCount() { - return getInt(R.string.pref_livemaphintshowcount, 0); - } - - public static void setLiveMapHintShowCount(final int showCount) { - putInt(R.string.pref_livemaphintshowcount, showCount); - } - - public static boolean isDbOnSDCard() { - return getBoolean(R.string.pref_dbonsdcard, false); - } - - public static void setDbOnSDCard(final boolean dbOnSDCard) { - putBoolean(R.string.pref_dbonsdcard, dbOnSDCard); - } - - public static String getGpxExportDir() { - return getString(R.string.pref_gpxExportDir, - Environment.getExternalStorageDirectory().getPath() + "/gpx"); - } - - public static String getGpxImportDir() { - return getString(R.string.pref_gpxImportDir, - Environment.getExternalStorageDirectory().getPath() + "/gpx"); - } - - public static boolean getShareAfterExport() { - return getBoolean(R.string.pref_shareafterexport, true); - } - - public static void setShareAfterExport(final boolean shareAfterExport) { - putBoolean(R.string.pref_shareafterexport, shareAfterExport); - } - - public static int getTrackableAction() { - return getInt(R.string.pref_trackableaction, LogTypeTrackable.RETRIEVED_IT.id); - } - - public static void setTrackableAction(final int trackableAction) { - putInt(R.string.pref_trackableaction, trackableAction); - } - - private static String getCustomRenderThemeBaseFolder() { - return getString(R.string.pref_renderthemepath, ""); - } - - public static String getCustomRenderThemeFilePath() { - return getString(R.string.pref_renderthemefile, ""); - } - - public static void setCustomRenderThemeFile(final String customRenderThemeFile) { - putString(R.string.pref_renderthemefile, customRenderThemeFile); - } - - public static File[] getMapThemeFiles() { - final File directory = new File(getCustomRenderThemeBaseFolder()); - final List<File> result = new ArrayList<>(); - FileUtils.listDir(result, directory, new ExtensionsBasedFileSelector(new String[] { "xml" }), null); - - return result.toArray(new File[result.size()]); - } - - private static class ExtensionsBasedFileSelector implements FileSelector { - private final String[] extensions; - public ExtensionsBasedFileSelector(final String[] extensions) { - this.extensions = extensions; - } - @Override - public boolean isSelected(final File file) { - final String filename = file.getName(); - for (final String ext : extensions) { - if (StringUtils.endsWithIgnoreCase(filename, ext)) { - return true; - } - } - return false; - } - @Override - public boolean shouldEnd() { - return false; - } - } - - /** - * @return true if plain text log wanted - */ - public static boolean getPlainLogs() { - return getBoolean(R.string.pref_plainLogs, false); - } - - /** - * Force set the plain text log preference - * - * @param plainLogs - * wanted or not - */ - public static void setPlainLogs(final boolean plainLogs) { - putBoolean(R.string.pref_plainLogs, plainLogs); - } - - public static boolean getUseNativeUa() { - return getBoolean(R.string.pref_nativeUa, false); - } - - public static String getCacheTwitterMessage() { - return getString(R.string.pref_twitter_cache_message, "I found [NAME] ([URL])."); - } - - public static String getTrackableTwitterMessage() { - return getString(R.string.pref_twitter_trackable_message, "I touched [NAME] ([URL])."); - } - - public static int getLogImageScale() { - return getInt(R.string.pref_logImageScale, -1); - } - - public static void setLogImageScale(final int scale) { - putInt(R.string.pref_logImageScale, scale); - } - - public static void setExcludeMine(final boolean exclude) { - putBoolean(R.string.pref_excludemine, exclude); - } - - static void setLogin(final String username, final String password) { - if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { - // erase username and password - remove(R.string.pref_username); - remove(R.string.pref_password); - return; - } - // save username and password - putString(R.string.pref_username, username); - putString(R.string.pref_password, password); - } - - public static long getFieldnoteExportDate() { - return getLong(R.string.pref_fieldNoteExportDate, 0); - } - - /** - * Remember date of last field note export. - */ - public static void setFieldnoteExportDate(final long date) { - putLong(R.string.pref_fieldNoteExportDate, date); - } - - public static boolean isUseNavigationApp(final NavigationAppsEnum navApp) { - return getBoolean(navApp.preferenceKey, true); - } - - /** - * Remember the state of the "Upload" checkbox in the field notes export dialog. - */ - public static void setFieldNoteExportUpload(final boolean upload) { - putBoolean(R.string.pref_fieldNoteExportUpload, upload); - } - - public static boolean getFieldNoteExportUpload() { - return getBoolean(R.string.pref_fieldNoteExportUpload, true); - } - - /** - * Remember the state of the "Only new" checkbox in the field notes export dialog. - */ - public static void setFieldNoteExportOnlyNew(final boolean onlyNew) { - putBoolean(R.string.pref_fieldNoteExportOnlyNew, onlyNew); - } - - public static boolean getFieldNoteExportOnlyNew() { - return getBoolean(R.string.pref_fieldNoteExportOnlyNew, false); - } - - public static String getECIconSet() { - return getString(R.string.pref_ec_icons, "1"); - } - - /* Store last checksum of changelog for changelog display */ - public static long getLastChangelogChecksum() { - return getLong(R.string.pref_changelog_last_checksum, 0); - } - - public static void setLastChangelogChecksum(final long checksum) { - putLong(R.string.pref_changelog_last_checksum, checksum); - } - - public static List<String> getLastOpenedCaches() { - final List<String> history = Arrays.asList(StringUtils.split(getString(R.string.pref_caches_history, StringUtils.EMPTY), HISTORY_SEPARATOR)); - return history.subList(0, Math.min(HISTORY_SIZE, history.size())); - } - - public static void addCacheToHistory(@NonNull final String geocode) { - final List<String> history = new ArrayList<>(getLastOpenedCaches()); - // bring entry to front, if it already existed - history.remove(geocode); - history.add(0, geocode); - putString(R.string.pref_caches_history, StringUtils.join(history, HISTORY_SEPARATOR)); - } - - public static boolean useHardwareAcceleration() { - return getBoolean(R.string.pref_hardware_acceleration, !HW_ACCEL_DISABLED_BY_DEFAULT); - } - - static void setUseHardwareAcceleration(final boolean useHardwareAcceleration) { - putBoolean(R.string.pref_hardware_acceleration, useHardwareAcceleration); - } - - public static String getLastCacheLog() { - return getString(R.string.pref_last_cache_log, StringUtils.EMPTY); - } - - public static void setLastCacheLog(final String log) { - putString(R.string.pref_last_cache_log, log); - } - - public static String getLastTrackableLog() { - return getString(R.string.pref_last_trackable_log, StringUtils.EMPTY); - } - - public static void setLastTrackableLog(final String log) { - putString(R.string.pref_last_trackable_log, log); - } - - @Nullable - public static String getHomeLocation() { - return getString(R.string.pref_home_location, null); - } - - public static void setHomeLocation(@NonNull final String homeLocation) { - putString(R.string.pref_home_location, homeLocation); - } - - public static void setForceOrientationSensor(final boolean forceOrientationSensor) { - putBoolean(R.string.pref_force_orientation_sensor, forceOrientationSensor); - } - - public static boolean useOrientationSensor(final Context context) { - return OrientationProvider.hasOrientationSensor(context) && (getBoolean(R.string.pref_force_orientation_sensor, false) || !RotationProvider.hasRotationSensor(context)); - } -} +package cgeo.geocaching.settings;
+
+import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.R;
+import cgeo.geocaching.apps.navi.NavigationAppFactory.NavigationAppsEnum;
+import cgeo.geocaching.connector.capability.ICredentials;
+import cgeo.geocaching.connector.gc.GCConnector;
+import cgeo.geocaching.connector.gc.GCConstants;
+import cgeo.geocaching.enumerations.CacheType;
+import cgeo.geocaching.enumerations.LogTypeTrackable;
+import cgeo.geocaching.list.StoredList;
+import cgeo.geocaching.location.Geopoint;
+import cgeo.geocaching.maps.CGeoMap.MapMode;
+import cgeo.geocaching.maps.LivemapStrategy;
+import cgeo.geocaching.maps.MapProviderFactory;
+import cgeo.geocaching.maps.google.v1.GoogleMapProvider;
+import cgeo.geocaching.maps.interfaces.GeoPointImpl;
+import cgeo.geocaching.maps.interfaces.MapProvider;
+import cgeo.geocaching.maps.interfaces.MapSource;
+import cgeo.geocaching.maps.mapsforge.MapsforgeMapProvider;
+import cgeo.geocaching.maps.mapsforge.MapsforgeMapProvider.OfflineMapSource;
+import cgeo.geocaching.sensors.OrientationProvider;
+import cgeo.geocaching.sensors.RotationProvider;
+import cgeo.geocaching.utils.CryptUtils;
+import cgeo.geocaching.utils.FileUtils;
+import cgeo.geocaching.utils.FileUtils.FileSelector;
+import cgeo.geocaching.utils.Log;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.Environment;
+import android.preference.PreferenceManager;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * General c:geo preferences/settings set by the user
+ */
+public class Settings {
+
+ /**
+ * On opening a map, we limit the _initial_ zoom. The user can still zoom out afterwards.
+ */
+ private static final int INITIAL_MAP_ZOOM_LIMIT = 16;
+ private static final char HISTORY_SEPARATOR = ',';
+ private static final int SHOW_WP_THRESHOLD_DEFAULT = 10;
+ public static final int SHOW_WP_THRESHOLD_MAX = 50;
+ private static final int MAP_SOURCE_DEFAULT = GoogleMapProvider.GOOGLE_MAP_ID.hashCode();
+
+ public static final boolean HW_ACCEL_DISABLED_BY_DEFAULT =
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 ||
+ StringUtils.equals(Build.MODEL, "HTC One X") || // HTC One X
+ StringUtils.equals(Build.MODEL, "HTC One S") || // HTC One S
+ StringUtils.equals(Build.MODEL, "GT-I8190") || // Samsung S3 mini
+ StringUtils.equals(Build.MODEL, "GT-S6310L") || // Samsung Galaxy Young
+ StringUtils.equals(Build.MODEL, "GT-P5210") || // Samsung Galaxy Tab 3
+ StringUtils.equals(Build.MODEL, "GT-S7580") || // Samsung Galaxy Trend Plus
+ StringUtils.equals(Build.MODEL, "GT-I9105P") || // Samsung Galaxy SII Plus
+ StringUtils.equals(Build.MODEL, "ST25i") || // Sony Xperia U
+ StringUtils.equals(Build.MODEL, "bq Aquaris 5") || // bq Aquaris 5
+ StringUtils.equals(Build.MODEL, "A1-810") || // Unknown A1-810
+ StringUtils.equals(Build.MODEL, "GT-I9195") || // Samsung S4 mini
+ StringUtils.equals(Build.MODEL, "GT-I8200N"); // Samsung S3 mini
+
+ // twitter api keys
+ private final static @NonNull String TWITTER_KEY_CONSUMER_PUBLIC = CryptUtils.rot13("ESnsCvAv3kEupF1GCR3jGj");
+ private final static @NonNull String TWITTER_KEY_CONSUMER_SECRET = CryptUtils.rot13("7vQWceACV9umEjJucmlpFe9FCMZSeqIqfkQ2BnhV9x");
+
+ private static boolean useCompass = true;
+
+ public enum CoordInputFormatEnum {
+ Plain,
+ Deg,
+ Min,
+ Sec;
+
+ static final int DEFAULT_INT_VALUE = Min.ordinal();
+
+ public static CoordInputFormatEnum fromInt(final int id) {
+ final CoordInputFormatEnum[] values = CoordInputFormatEnum.values();
+ if (id < 0 || id >= values.length) {
+ return Min;
+ }
+ return values[id];
+ }
+ }
+
+ private static final SharedPreferences sharedPrefs = PreferenceManager
+ .getDefaultSharedPreferences(CgeoApplication.getInstance().getBaseContext());
+ static {
+ migrateSettings();
+ final boolean isDebug = sharedPrefs.getBoolean(getKey(R.string.pref_debug), false);
+ Log.setDebug(isDebug);
+ CgeoApplication.dumpOnOutOfMemory(isDebug);
+ }
+
+ /**
+ * Cache the mapsource locally. If that is an offline map source, each request would potentially access the
+ * underlying map file, leading to delays.
+ */
+ private static MapSource mapSource;
+
+ protected Settings() {
+ throw new InstantiationError();
+ }
+
+ private static void migrateSettings() {
+ final int LATEST_PREFERENCES_VERSION = 2;
+ final int currentVersion = getInt(R.string.pref_settingsversion, 0);
+
+ // No need to migrate if we are up to date.
+ if (currentVersion == LATEST_PREFERENCES_VERSION) {
+ return;
+ }
+
+ // No need to migrate if we don't have older settings, defaults will be used instead.
+ final String preferencesNameV0 = "cgeo.pref";
+ final SharedPreferences prefsV0 = CgeoApplication.getInstance().getSharedPreferences(preferencesNameV0, Context.MODE_PRIVATE);
+ if (currentVersion == 0 && prefsV0.getAll().isEmpty()) {
+ final Editor e = sharedPrefs.edit();
+ e.putInt(getKey(R.string.pref_settingsversion), LATEST_PREFERENCES_VERSION);
+ e.apply();
+ return;
+ }
+
+ if (currentVersion < 1) {
+ // migrate from non standard file location and integer based boolean types
+ final Editor e = sharedPrefs.edit();
+
+ e.putString(getKey(R.string.pref_temp_twitter_token_secret), prefsV0.getString(getKey(R.string.pref_temp_twitter_token_secret), null));
+ e.putString(getKey(R.string.pref_temp_twitter_token_public), prefsV0.getString(getKey(R.string.pref_temp_twitter_token_public), null));
+ e.putBoolean(getKey(R.string.pref_help_shown), prefsV0.getInt(getKey(R.string.pref_help_shown), 0) != 0);
+ e.putFloat(getKey(R.string.pref_anylongitude), prefsV0.getFloat(getKey(R.string.pref_anylongitude), 0));
+ e.putFloat(getKey(R.string.pref_anylatitude), prefsV0.getFloat(getKey(R.string.pref_anylatitude), 0));
+ e.putBoolean(getKey(R.string.pref_offlinemaps), 0 != prefsV0.getInt(getKey(R.string.pref_offlinemaps), 1));
+ e.putBoolean(getKey(R.string.pref_offlinewpmaps), 0 != prefsV0.getInt(getKey(R.string.pref_offlinewpmaps), 0));
+ e.putString(getKey(R.string.pref_webDeviceCode), prefsV0.getString(getKey(R.string.pref_webDeviceCode), null));
+ e.putString(getKey(R.string.pref_webDeviceName), prefsV0.getString(getKey(R.string.pref_webDeviceName), null));
+ e.putBoolean(getKey(R.string.pref_maplive), prefsV0.getInt(getKey(R.string.pref_maplive), 1) != 0);
+ e.putInt(getKey(R.string.pref_mapsource), prefsV0.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT));
+ e.putBoolean(getKey(R.string.pref_twitter), 0 != prefsV0.getInt(getKey(R.string.pref_twitter), 0));
+ e.putBoolean(getKey(R.string.pref_showaddress), 0 != prefsV0.getInt(getKey(R.string.pref_showaddress), 1));
+ e.putBoolean(getKey(R.string.pref_showcaptcha), prefsV0.getBoolean(getKey(R.string.pref_showcaptcha), false));
+ e.putBoolean(getKey(R.string.pref_maptrail), prefsV0.getInt(getKey(R.string.pref_maptrail), 1) != 0);
+ e.putInt(getKey(R.string.pref_lastmapzoom), prefsV0.getInt(getKey(R.string.pref_lastmapzoom), 14));
+ e.putBoolean(getKey(R.string.pref_livelist), 0 != prefsV0.getInt(getKey(R.string.pref_livelist), 1));
+ e.putBoolean(getKey(R.string.pref_units_imperial), prefsV0.getInt(getKey(R.string.pref_units_imperial), 1) != 1);
+ e.putBoolean(getKey(R.string.pref_skin), prefsV0.getInt(getKey(R.string.pref_skin), 0) != 0);
+ e.putInt(getKey(R.string.pref_lastusedlist), prefsV0.getInt(getKey(R.string.pref_lastusedlist), StoredList.STANDARD_LIST_ID));
+ e.putString(getKey(R.string.pref_cachetype), prefsV0.getString(getKey(R.string.pref_cachetype), CacheType.ALL.id));
+ e.putString(getKey(R.string.pref_twitter_token_secret), prefsV0.getString(getKey(R.string.pref_twitter_token_secret), null));
+ e.putString(getKey(R.string.pref_twitter_token_public), prefsV0.getString(getKey(R.string.pref_twitter_token_public), null));
+ e.putInt(getKey(R.string.pref_version), prefsV0.getInt(getKey(R.string.pref_version), 0));
+ e.putBoolean(getKey(R.string.pref_autoloaddesc), 0 != prefsV0.getInt(getKey(R.string.pref_autoloaddesc), 1));
+ e.putBoolean(getKey(R.string.pref_ratingwanted), prefsV0.getBoolean(getKey(R.string.pref_ratingwanted), true));
+ e.putBoolean(getKey(R.string.pref_friendlogswanted), prefsV0.getBoolean(getKey(R.string.pref_friendlogswanted), true));
+ e.putBoolean(getKey(R.string.pref_useenglish), prefsV0.getBoolean(getKey(R.string.pref_useenglish), false));
+ e.putBoolean(getKey(R.string.pref_usecompass), 0 != prefsV0.getInt(getKey(R.string.pref_usecompass), 1));
+ e.putBoolean(getKey(R.string.pref_trackautovisit), prefsV0.getBoolean(getKey(R.string.pref_trackautovisit), false));
+ e.putBoolean(getKey(R.string.pref_sigautoinsert), prefsV0.getBoolean(getKey(R.string.pref_sigautoinsert), false));
+ e.putBoolean(getKey(R.string.pref_logimages), prefsV0.getBoolean(getKey(R.string.pref_logimages), false));
+ e.putBoolean(getKey(R.string.pref_excludedisabled), 0 != prefsV0.getInt(getKey(R.string.pref_excludedisabled), 0));
+ e.putBoolean(getKey(R.string.pref_excludemine), 0 != prefsV0.getInt(getKey(R.string.pref_excludemine), 0));
+ e.putString(getKey(R.string.pref_mapfile), prefsV0.getString(getKey(R.string.pref_mapfile), null));
+ e.putString(getKey(R.string.pref_signature), prefsV0.getString(getKey(R.string.pref_signature), null));
+ e.putString(getKey(R.string.pref_pass_vote), prefsV0.getString(getKey(R.string.pref_pass_vote), null));
+ e.putString(getKey(R.string.pref_password), prefsV0.getString(getKey(R.string.pref_password), null));
+ e.putString(getKey(R.string.pref_username), prefsV0.getString(getKey(R.string.pref_username), null));
+ e.putString(getKey(R.string.pref_memberstatus), prefsV0.getString(getKey(R.string.pref_memberstatus), ""));
+ e.putInt(getKey(R.string.pref_coordinputformat), prefsV0.getInt(getKey(R.string.pref_coordinputformat), CoordInputFormatEnum.DEFAULT_INT_VALUE));
+ e.putBoolean(getKey(R.string.pref_log_offline), prefsV0.getBoolean(getKey(R.string.pref_log_offline), false));
+ e.putBoolean(getKey(R.string.pref_choose_list), prefsV0.getBoolean(getKey(R.string.pref_choose_list), true));
+ e.putBoolean(getKey(R.string.pref_loaddirectionimg), prefsV0.getBoolean(getKey(R.string.pref_loaddirectionimg), true));
+ e.putString(getKey(R.string.pref_gccustomdate), prefsV0.getString(getKey(R.string.pref_gccustomdate), GCConstants.DEFAULT_GC_DATE));
+ e.putInt(getKey(R.string.pref_showwaypointsthreshold), prefsV0.getInt(getKey(R.string.pref_showwaypointsthreshold), SHOW_WP_THRESHOLD_DEFAULT));
+ e.putString(getKey(R.string.pref_cookiestore), prefsV0.getString(getKey(R.string.pref_cookiestore), null));
+ e.putBoolean(getKey(R.string.pref_opendetailslastpage), prefsV0.getBoolean(getKey(R.string.pref_opendetailslastpage), false));
+ e.putInt(getKey(R.string.pref_lastdetailspage), prefsV0.getInt(getKey(R.string.pref_lastdetailspage), 1));
+ e.putInt(getKey(R.string.pref_defaultNavigationTool), prefsV0.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id));
+ e.putInt(getKey(R.string.pref_defaultNavigationTool2), prefsV0.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id));
+ e.putInt(getKey(R.string.pref_livemapstrategy), prefsV0.getInt(getKey(R.string.pref_livemapstrategy), LivemapStrategy.AUTO.id));
+ e.putBoolean(getKey(R.string.pref_debug), prefsV0.getBoolean(getKey(R.string.pref_debug), false));
+ e.putInt(getKey(R.string.pref_livemaphintshowcount), prefsV0.getInt(getKey(R.string.pref_livemaphintshowcount), 0));
+
+ e.putInt(getKey(R.string.pref_settingsversion), 1); // mark migrated
+ e.apply();
+ }
+
+ // changes for new settings dialog
+ if (currentVersion < 2) {
+ final Editor e = sharedPrefs.edit();
+
+ e.putBoolean(getKey(R.string.pref_units_imperial), useImperialUnits());
+
+ // show waypoints threshold now as a slider
+ int wpThreshold = getWayPointsThreshold();
+ if (wpThreshold < 0) {
+ wpThreshold = 0;
+ } else if (wpThreshold > SHOW_WP_THRESHOLD_MAX) {
+ wpThreshold = SHOW_WP_THRESHOLD_MAX;
+ }
+ e.putInt(getKey(R.string.pref_showwaypointsthreshold), wpThreshold);
+
+ // KEY_MAP_SOURCE must be string, because it is the key for a ListPreference now
+ final int ms = sharedPrefs.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT);
+ e.remove(getKey(R.string.pref_mapsource));
+ e.putString(getKey(R.string.pref_mapsource), String.valueOf(ms));
+
+ // navigation tool ids must be string, because ListPreference uses strings as keys
+ final int dnt1 = sharedPrefs.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id);
+ final int dnt2 = sharedPrefs.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id);
+ e.remove(getKey(R.string.pref_defaultNavigationTool));
+ e.remove(getKey(R.string.pref_defaultNavigationTool2));
+ e.putString(getKey(R.string.pref_defaultNavigationTool), String.valueOf(dnt1));
+ e.putString(getKey(R.string.pref_defaultNavigationTool2), String.valueOf(dnt2));
+
+ // defaults for gpx directories
+ e.putString(getKey(R.string.pref_gpxImportDir), getGpxImportDir());
+ e.putString(getKey(R.string.pref_gpxExportDir), getGpxExportDir());
+
+ e.putInt(getKey(R.string.pref_settingsversion), 2); // mark migrated
+ e.apply();
+ }
+ }
+
+ private static String getKey(final int prefKeyId) {
+ return CgeoApplication.getInstance().getString(prefKeyId);
+ }
+
+ static String getString(final int prefKeyId, final String defaultValue) {
+ return sharedPrefs.getString(getKey(prefKeyId), defaultValue);
+ }
+
+ private static int getInt(final int prefKeyId, final int defaultValue) {
+ return sharedPrefs.getInt(getKey(prefKeyId), defaultValue);
+ }
+
+ private static long getLong(final int prefKeyId, final long defaultValue) {
+ return sharedPrefs.getLong(getKey(prefKeyId), defaultValue);
+ }
+
+ private static boolean getBoolean(final int prefKeyId, final boolean defaultValue) {
+ return sharedPrefs.getBoolean(getKey(prefKeyId), defaultValue);
+ }
+
+ private static float getFloat(final int prefKeyId, final float defaultValue) {
+ return sharedPrefs.getFloat(getKey(prefKeyId), defaultValue);
+ }
+
+ protected static void putString(final int prefKeyId, final String value) {
+ final SharedPreferences.Editor edit = sharedPrefs.edit();
+ edit.putString(getKey(prefKeyId), value);
+ edit.apply();
+ }
+
+ protected static void putBoolean(final int prefKeyId, final boolean value) {
+ final SharedPreferences.Editor edit = sharedPrefs.edit();
+ edit.putBoolean(getKey(prefKeyId), value);
+ edit.apply();
+ }
+
+ private static void putInt(final int prefKeyId, final int value) {
+ final SharedPreferences.Editor edit = sharedPrefs.edit();
+ edit.putInt(getKey(prefKeyId), value);
+ edit.apply();
+ }
+
+ private static void putLong(final int prefKeyId, final long value) {
+ final SharedPreferences.Editor edit = sharedPrefs.edit();
+ edit.putLong(getKey(prefKeyId), value);
+ edit.apply();
+ }
+
+ private static void putFloat(final int prefKeyId, final float value) {
+ final SharedPreferences.Editor edit = sharedPrefs.edit();
+ edit.putFloat(getKey(prefKeyId), value);
+ edit.apply();
+ }
+
+ private static void remove(final int prefKeyId) {
+ final SharedPreferences.Editor edit = sharedPrefs.edit();
+ edit.remove(getKey(prefKeyId));
+ edit.apply();
+ }
+
+ private static boolean contains(final int prefKeyId) {
+ return sharedPrefs.contains(getKey(prefKeyId));
+ }
+
+ public static boolean hasGCCredentials() {
+ final String preUsername = getString(R.string.pref_username, null);
+ final String prePassword = getString(R.string.pref_password, null);
+
+ return !StringUtils.isBlank(preUsername) && !StringUtils.isBlank(prePassword);
+ }
+
+ /**
+ * Get login and password information of Geocaching.com.
+ *
+ * @return a pair either with (login, password) or (empty, empty) if no valid information is stored
+ */
+ public static ImmutablePair<String, String> getGcCredentials() {
+ return getCredentials(GCConnector.getInstance());
+ }
+
+ /**
+ * Get login and password information.
+ *
+ * @return a pair either with (login, password) or (empty, empty) if no valid information is stored
+ */
+ public static ImmutablePair<String, String> getCredentials(final @NonNull ICredentials connector) {
+ final String username = getString(connector.getUsernamePreferenceKey(), null);
+ final String password = getString(connector.getPasswordPreferenceKey(), null);
+
+ if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
+ return new ImmutablePair<>(StringUtils.EMPTY, StringUtils.EMPTY);
+ }
+
+ return new ImmutablePair<>(username, password);
+ }
+
+ public static String getUsername() {
+ return getString(R.string.pref_username, StringUtils.EMPTY);
+ }
+
+ public static boolean isGCConnectorActive() {
+ return getBoolean(R.string.pref_connectorGCActive, true);
+ }
+
+ public static boolean isECConnectorActive() {
+ return getBoolean(R.string.pref_connectorECActive, false);
+ }
+
+ public static boolean isGCPremiumMember() {
+ final String memberStatus = getGCMemberStatus();
+ return StringUtils.equalsIgnoreCase(memberStatus, GCConstants.MEMBER_STATUS_PREMIUM) ||
+ StringUtils.equalsIgnoreCase(memberStatus, GCConstants.MEMBER_STATUS_CHARTER);
+ }
+
+ public static String getGCMemberStatus() {
+ return getString(R.string.pref_memberstatus, "");
+ }
+
+ public static void setGCMemberStatus(final String memberStatus) {
+ if (StringUtils.isBlank(memberStatus)) {
+ remove(R.string.pref_memberstatus);
+ }
+ putString(R.string.pref_memberstatus, memberStatus);
+ }
+
+ public static ImmutablePair<String, String> getTokenPair(final int tokenPublicPrefKey, final int tokenSecretPrefKey) {
+ return new ImmutablePair<>(getString(tokenPublicPrefKey, null), getString(tokenSecretPrefKey, null));
+ }
+
+ public static void setTokens(final int tokenPublicPrefKey, @Nullable final String tokenPublic, final int tokenSecretPrefKey, @Nullable final String tokenSecret) {
+ if (tokenPublic == null) {
+ remove(tokenPublicPrefKey);
+ } else {
+ putString(tokenPublicPrefKey, tokenPublic);
+ }
+ if (tokenSecret == null) {
+ remove(tokenSecretPrefKey);
+ } else {
+ putString(tokenSecretPrefKey, tokenSecret);
+ }
+ }
+
+ public static boolean isOCConnectorActive(final int isActivePrefKeyId) {
+ return getBoolean(isActivePrefKeyId, false);
+ }
+
+ public static boolean hasOCAuthorization(final int tokenPublicPrefKeyId, final int tokenSecretPrefKeyId) {
+ return StringUtils.isNotBlank(getString(tokenPublicPrefKeyId, ""))
+ && StringUtils.isNotBlank(getString(tokenSecretPrefKeyId, ""));
+ }
+
+ public static boolean isGCVoteLogin() {
+ return getGCVoteLogin() != null;
+ }
+
+ public static ImmutablePair<String, String> getGCVoteLogin() {
+ final String username = getString(R.string.pref_username, null);
+ final String password = getString(R.string.pref_pass_vote, null);
+
+ if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
+ return null;
+ }
+
+ return new ImmutablePair<>(username, password);
+ }
+
+ @NonNull
+ public static String getSignature() {
+ return StringUtils.defaultString(getString(R.string.pref_signature, StringUtils.EMPTY));
+ }
+
+ public static void setCookieStore(final String cookies) {
+ if (StringUtils.isBlank(cookies)) {
+ // erase cookies
+ remove(R.string.pref_cookiestore);
+ }
+ // save cookies
+ putString(R.string.pref_cookiestore, cookies);
+ }
+
+ public static String getCookieStore() {
+ return getString(R.string.pref_cookiestore, null);
+ }
+
+ public static void setUseGooglePlayServices(final boolean value) {
+ putBoolean(R.string.pref_googleplayservices, value);
+ }
+
+ public static boolean useGooglePlayServices() {
+ // By defaut, enable play services starting from ICS.
+ return CgeoApplication.getInstance().isGooglePlayServicesAvailable() &&
+ getBoolean(R.string.pref_googleplayservices, VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH);
+ }
+
+ public static boolean useLowPowerMode() {
+ return getBoolean(R.string.pref_lowpowermode, false);
+ }
+
+ /**
+ * @param cacheType
+ * The cache type used for future filtering
+ */
+ public static void setCacheType(final CacheType cacheType) {
+ if (cacheType == null) {
+ remove(R.string.pref_cachetype);
+ } else {
+ putString(R.string.pref_cachetype, cacheType.id);
+ }
+ }
+
+ public static int getLastList() {
+ return getInt(R.string.pref_lastusedlist, StoredList.STANDARD_LIST_ID);
+ }
+
+ public static void saveLastList(final int listId) {
+ putInt(R.string.pref_lastusedlist, listId);
+ }
+
+ public static void setWebNameCode(final String name, final String code) {
+ putString(R.string.pref_webDeviceName, name);
+ putString(R.string.pref_webDeviceCode, code);
+ }
+
+ public static MapProvider getMapProvider() {
+ return getMapSource().getMapProvider();
+ }
+
+ public static String getMapFile() {
+ return getString(R.string.pref_mapfile, null);
+ }
+
+ static void setMapFile(final String mapFile) {
+ putString(R.string.pref_mapfile, mapFile);
+ if (mapFile != null) {
+ setMapFileDirectory(new File(mapFile).getParent());
+ }
+ }
+
+ public static String getMapFileDirectory() {
+ final String mapDir = getString(R.string.pref_mapDirectory, null);
+ if (mapDir != null) {
+ return mapDir;
+ }
+ final String mapFile = getMapFile();
+ if (mapFile != null) {
+ return new File(mapFile).getParent();
+ }
+ return null;
+ }
+
+ static void setMapFileDirectory(final String mapFileDirectory) {
+ putString(R.string.pref_mapDirectory, mapFileDirectory);
+ MapsforgeMapProvider.getInstance().updateOfflineMaps();
+ }
+
+ private static boolean isValidMapFile() {
+ return isValidMapFile(getMapFile());
+ }
+
+ public static boolean isValidMapFile(final String mapFileIn) {
+ return MapsforgeMapProvider.isValidMapFile(mapFileIn);
+ }
+
+ public static boolean isScaleMapsforgeText() {
+ return getBoolean(R.string.pref_mapsforge_scale_text, true);
+ }
+
+ public static CoordInputFormatEnum getCoordInputFormat() {
+ return CoordInputFormatEnum.fromInt(getInt(R.string.pref_coordinputformat, CoordInputFormatEnum.DEFAULT_INT_VALUE));
+ }
+
+ public static void setCoordInputFormat(final CoordInputFormatEnum format) {
+ putInt(R.string.pref_coordinputformat, format.ordinal());
+ }
+
+ public static boolean getLogOffline() {
+ return getBoolean(R.string.pref_log_offline, false);
+ }
+
+ public static boolean getChooseList() {
+ return getBoolean(R.string.pref_choose_list, false);
+ }
+
+ public static boolean getLoadDirImg() {
+ return !isGCPremiumMember() && getBoolean(R.string.pref_loaddirectionimg, true);
+ }
+
+ public static void setGcCustomDate(final String format) {
+ putString(R.string.pref_gccustomdate, format);
+ }
+
+ /**
+ * @return User selected date format on GC.com
+ */
+ public static String getGcCustomDate() {
+ // We might have some users whose stored value is null, which is invalid. In this case, we use the default.
+ return StringUtils.defaultString(getString(R.string.pref_gccustomdate, GCConstants.DEFAULT_GC_DATE),
+ GCConstants.DEFAULT_GC_DATE);
+ }
+
+ public static boolean isExcludeMyCaches() {
+ return getBoolean(R.string.pref_excludemine, false);
+ }
+
+ public static boolean useEnglish() {
+ return getBoolean(R.string.pref_useenglish, false);
+ }
+
+ public static boolean isShowAddress() {
+ return getBoolean(R.string.pref_showaddress, true);
+ }
+
+ public static boolean isShowCaptcha() {
+ return !isGCPremiumMember() && getBoolean(R.string.pref_showcaptcha, false);
+ }
+
+ public static boolean isExcludeDisabledCaches() {
+ return getBoolean(R.string.pref_excludedisabled, false);
+ }
+
+ public static boolean isStoreOfflineMaps() {
+ return getBoolean(R.string.pref_offlinemaps, true);
+ }
+
+ public static boolean isStoreOfflineWpMaps() {
+ return getBoolean(R.string.pref_offlinewpmaps, false);
+ }
+
+ public static boolean isStoreLogImages() {
+ return getBoolean(R.string.pref_logimages, false);
+ }
+
+ public static boolean isAutoLoadDescription() {
+ return getBoolean(R.string.pref_autoloaddesc, true);
+ }
+
+ public static boolean isRatingWanted() {
+ return getBoolean(R.string.pref_ratingwanted, true);
+ }
+
+ public static boolean isGeokretyConnectorActive() {
+ return getBoolean(R.string.pref_connectorGeokretyActive, false);
+ }
+
+ public static boolean isGeokretyCacheActive() {
+ return getBoolean(R.string.pref_geokrety_cache, true);
+ }
+
+ static boolean hasGeokretyAuthorization() {
+ return StringUtils.isNotBlank(getGeokretySecId());
+ }
+
+ public static String getGeokretySecId() {
+ return getString(R.string.pref_fakekey_geokrety_authorization, null);
+ }
+
+ public static void setGeokretySecId(final String secid) {
+ putString(R.string.pref_fakekey_geokrety_authorization, secid);
+ }
+
+ public static boolean isRegisteredForGeokretyLogging() {
+ return getGeokretySecId() != null;
+ }
+
+ /**
+ * Retrieve showed popup counter for warning about logging Trackable recommend Geocode
+ *
+ * @return number of times the popup has appeared
+ */
+ public static int getLogTrackableWithoutGeocodeShowCount() {
+ return getInt(R.string.pref_logtrackablewithoutgeocodeshowcount, 0);
+ }
+
+ /**
+ * Store showed popup counter for warning about logging Trackable recommend Geocode
+ *
+ * @param showCount the count to save
+ */
+ public static void setLogTrackableWithoutGeocodeShowCount(final int showCount) {
+ putInt(R.string.pref_logtrackablewithoutgeocodeshowcount, showCount);
+ }
+
+ public static boolean isFriendLogsWanted() {
+ if (!hasGCCredentials()) {
+ // don't show a friends log if the user is anonymous
+ return false;
+ }
+ return getBoolean(R.string.pref_friendlogswanted, true);
+ }
+
+ public static boolean isLiveList() {
+ return getBoolean(R.string.pref_livelist, true);
+ }
+
+ public static boolean isTrackableAutoVisit() {
+ return getBoolean(R.string.pref_trackautovisit, false);
+ }
+
+ public static boolean isAutoInsertSignature() {
+ return getBoolean(R.string.pref_sigautoinsert, false);
+ }
+
+ static void setUseImperialUnits(final boolean useImperialUnits) {
+ putBoolean(R.string.pref_units_imperial, useImperialUnits);
+ }
+
+ public static boolean useImperialUnits() {
+ return getBoolean(R.string.pref_units_imperial, useImperialUnitsByDefault());
+ }
+
+ private static boolean useImperialUnitsByDefault() {
+ final String countryCode = Locale.getDefault().getCountry();
+ return "US".equals(countryCode) // USA
+ || "LR".equals(countryCode) // Liberia
+ || "MM".equals(countryCode); // Burma
+ }
+
+ public static boolean isLiveMap() {
+ return getBoolean(R.string.pref_maplive, true);
+ }
+
+ public static void setLiveMap(final boolean live) {
+ putBoolean(R.string.pref_maplive, live);
+ }
+
+ public static boolean isMapTrail() {
+ return getBoolean(R.string.pref_maptrail, false);
+ }
+
+ public static void setMapTrail(final boolean showTrail) {
+ putBoolean(R.string.pref_maptrail, showTrail);
+ }
+
+ /**
+ * whether to show a direction line on the map
+ */
+ public static boolean isMapDirection() {
+ return getBoolean(R.string.pref_map_direction, true);
+ }
+
+ public static void setMapDirection(final boolean showDirection) {
+ putBoolean(R.string.pref_map_direction, showDirection);
+ }
+
+ /**
+ * Get last used zoom of the internal map. Differentiate between two use cases for a map of multiple caches (e.g.
+ * live map) and the map of a single cache (which is often zoomed in more deep).
+ */
+ public static int getMapZoom(final MapMode mapMode) {
+ if (mapMode == MapMode.SINGLE || mapMode == MapMode.COORDS) {
+ return getCacheZoom();
+ }
+ return getMapZoom();
+ }
+
+ public static void setMapZoom(final MapMode mapMode, final int zoomLevel) {
+ if (mapMode == MapMode.SINGLE || mapMode == MapMode.COORDS) {
+ setCacheZoom(zoomLevel);
+ }
+ else {
+ setMapZoom(zoomLevel);
+ }
+ }
+
+ /**
+ * @return zoom used for the (live) map
+ */
+ private static int getMapZoom() {
+ return Math.max(getInt(R.string.pref_lastmapzoom, 14), INITIAL_MAP_ZOOM_LIMIT);
+ }
+
+ private static void setMapZoom(final int mapZoomLevel) {
+ putInt(R.string.pref_lastmapzoom, mapZoomLevel);
+ }
+
+ /**
+ * @return zoom used for the map of a single cache
+ */
+ private static int getCacheZoom() {
+ return Math.max(getInt(R.string.pref_cache_zoom, 14), INITIAL_MAP_ZOOM_LIMIT);
+ }
+
+ private static void setCacheZoom(final int zoomLevel) {
+ putInt(R.string.pref_cache_zoom, zoomLevel);
+ }
+
+ public static GeoPointImpl getMapCenter() {
+ return getMapProvider().getMapItemFactory()
+ .getGeoPointBase(new Geopoint(getInt(R.string.pref_lastmaplat, 0) / 1e6,
+ getInt(R.string.pref_lastmaplon, 0) / 1e6));
+ }
+
+ public static void setMapCenter(final GeoPointImpl mapViewCenter) {
+ putInt(R.string.pref_lastmaplat, mapViewCenter.getLatitudeE6());
+ putInt(R.string.pref_lastmaplon, mapViewCenter.getLongitudeE6());
+ }
+
+ @NonNull
+ public static synchronized MapSource getMapSource() {
+ if (mapSource != null) {
+ return mapSource;
+ }
+ final int id = getConvertedMapId();
+ mapSource = MapProviderFactory.getMapSource(id);
+ if (mapSource != null) {
+ // don't use offline maps if the map file is not valid
+ if (!(mapSource instanceof OfflineMapSource) || isValidMapFile()) {
+ return mapSource;
+ }
+ }
+ // fallback to first available map
+ return MapProviderFactory.getDefaultSource();
+ }
+
+ private final static int GOOGLEMAP_BASEID = 30;
+ private final static int MAP = 1;
+ private final static int SATELLITE = 2;
+
+ private final static int MFMAP_BASEID = 40;
+ private final static int MAPNIK = 1;
+ private final static int CYCLEMAP = 3;
+ private final static int OFFLINE = 4;
+ private static final int HISTORY_SIZE = 10;
+
+ /**
+ * Convert old preference ids for maps (based on constant values) into new hash based ids.
+ */
+ private static int getConvertedMapId() {
+ final int id = Integer.parseInt(getString(R.string.pref_mapsource,
+ String.valueOf(MAP_SOURCE_DEFAULT)));
+ switch (id) {
+ case GOOGLEMAP_BASEID + MAP:
+ return GoogleMapProvider.GOOGLE_MAP_ID.hashCode();
+ case GOOGLEMAP_BASEID + SATELLITE:
+ return GoogleMapProvider.GOOGLE_SATELLITE_ID.hashCode();
+ case MFMAP_BASEID + MAPNIK:
+ return MapsforgeMapProvider.MAPSFORGE_MAPNIK_ID.hashCode();
+ case MFMAP_BASEID + CYCLEMAP:
+ return MapsforgeMapProvider.MAPSFORGE_CYCLEMAP_ID.hashCode();
+ case MFMAP_BASEID + OFFLINE: {
+ final String mapFile = getMapFile();
+ if (StringUtils.isNotEmpty(mapFile)) {
+ return mapFile.hashCode();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return id;
+ }
+
+ public static synchronized void setMapSource(final MapSource newMapSource) {
+ putString(R.string.pref_mapsource, String.valueOf(newMapSource.getNumericalId()));
+ if (newMapSource instanceof OfflineMapSource) {
+ setMapFile(((OfflineMapSource) newMapSource).getFileName());
+ }
+ // cache the value
+ mapSource = newMapSource;
+ }
+
+ public static void setAnyCoordinates(final Geopoint coords) {
+ if (null != coords) {
+ putFloat(R.string.pref_anylatitude, (float) coords.getLatitude());
+ putFloat(R.string.pref_anylongitude, (float) coords.getLongitude());
+ } else {
+ remove(R.string.pref_anylatitude);
+ remove(R.string.pref_anylongitude);
+ }
+ }
+
+ public static Geopoint getAnyCoordinates() {
+ if (contains(R.string.pref_anylatitude) && contains(R.string.pref_anylongitude)) {
+ final float lat = getFloat(R.string.pref_anylatitude, 0);
+ final float lon = getFloat(R.string.pref_anylongitude, 0);
+ return new Geopoint(lat, lon);
+ }
+ return null;
+ }
+
+ public static boolean isUseCompass() {
+ return useCompass;
+ }
+
+ public static void setUseCompass(final boolean value) {
+ useCompass = value;
+ }
+
+ public static boolean isLightSkin() {
+ return getBoolean(R.string.pref_skin, false);
+ }
+
+ @NonNull
+ public static String getTwitterKeyConsumerPublic() {
+ return TWITTER_KEY_CONSUMER_PUBLIC;
+ }
+
+ @NonNull
+ public static String getTwitterKeyConsumerSecret() {
+ return TWITTER_KEY_CONSUMER_SECRET;
+ }
+
+ public static String getWebDeviceCode() {
+ return getString(R.string.pref_webDeviceCode, null);
+ }
+
+ public static boolean isRegisteredForSend2cgeo() {
+ return getWebDeviceCode() != null;
+ }
+
+ static String getWebDeviceName() {
+ return getString(R.string.pref_webDeviceName, Build.MODEL);
+ }
+
+ /**
+ * @return The cache type used for filtering or ALL if no filter is active.
+ * Returns never null
+ */
+ @NonNull
+ public static CacheType getCacheType() {
+ return CacheType.getById(getString(R.string.pref_cachetype, CacheType.ALL.id));
+ }
+
+ /**
+ * The Threshold for the showing of child waypoints
+ */
+ public static int getWayPointsThreshold() {
+ return getInt(R.string.pref_showwaypointsthreshold, SHOW_WP_THRESHOLD_DEFAULT);
+ }
+
+ static void setShowWaypointsThreshold(final int threshold) {
+ putInt(R.string.pref_showwaypointsthreshold, threshold);
+ }
+
+ public static boolean isUseTwitter() {
+ return getBoolean(R.string.pref_twitter, false);
+ }
+
+ private static void setUseTwitter(final boolean useTwitter) {
+ putBoolean(R.string.pref_twitter, useTwitter);
+ }
+
+ public static boolean isTwitterLoginValid() {
+ return !StringUtils.isBlank(getTokenPublic())
+ && !StringUtils.isBlank(getTokenSecret());
+ }
+
+ public static String getTokenPublic() {
+ return getString(R.string.pref_twitter_token_public, null);
+ }
+
+ public static String getTokenSecret() {
+ return getString(R.string.pref_twitter_token_secret, null);
+
+ }
+
+ static boolean hasTwitterAuthorization() {
+ return StringUtils.isNotBlank(getTokenPublic())
+ && StringUtils.isNotBlank(getTokenSecret());
+ }
+
+ public static void setTwitterTokens(@Nullable final String tokenPublic,
+ @Nullable final String tokenSecret, final boolean enableTwitter) {
+ putString(R.string.pref_twitter_token_public, tokenPublic);
+ putString(R.string.pref_twitter_token_secret, tokenSecret);
+ if (tokenPublic != null) {
+ remove(R.string.pref_temp_twitter_token_public);
+ remove(R.string.pref_temp_twitter_token_secret);
+ }
+ setUseTwitter(enableTwitter);
+ }
+
+ public static void setTwitterTempTokens(@Nullable final String tokenPublic,
+ @Nullable final String tokenSecret) {
+ putString(R.string.pref_temp_twitter_token_public, tokenPublic);
+ putString(R.string.pref_temp_twitter_token_secret, tokenSecret);
+ }
+
+ public static ImmutablePair<String, String> getTempToken() {
+ final String tokenPublic = getString(R.string.pref_temp_twitter_token_public, null);
+ final String tokenSecret = getString(R.string.pref_temp_twitter_token_secret, null);
+ return new ImmutablePair<>(tokenPublic, tokenSecret);
+ }
+
+ public static int getVersion() {
+ return getInt(R.string.pref_version, 0);
+ }
+
+ public static void setVersion(final int version) {
+ putInt(R.string.pref_version, version);
+ }
+
+ public static boolean isOpenLastDetailsPage() {
+ return getBoolean(R.string.pref_opendetailslastpage, false);
+ }
+
+ public static int getLastDetailsPage() {
+ return getInt(R.string.pref_lastdetailspage, 1);
+ }
+
+ public static void setLastDetailsPage(final int index) {
+ putInt(R.string.pref_lastdetailspage, index);
+ }
+
+ public static int getDefaultNavigationTool() {
+ return Integer.parseInt(getString(
+ R.string.pref_defaultNavigationTool,
+ String.valueOf(NavigationAppsEnum.COMPASS.id)));
+ }
+
+ public static int getDefaultNavigationTool2() {
+ return Integer.parseInt(getString(
+ R.string.pref_defaultNavigationTool2,
+ String.valueOf(NavigationAppsEnum.INTERNAL_MAP.id)));
+ }
+
+ public static LivemapStrategy getLiveMapStrategy() {
+ return LivemapStrategy.getById(getInt(R.string.pref_livemapstrategy, LivemapStrategy.AUTO.id));
+ }
+
+ public static void setLiveMapStrategy(final LivemapStrategy strategy) {
+ putInt(R.string.pref_livemapstrategy, strategy.id);
+ }
+
+ public static boolean isDebug() {
+ return Log.isDebug();
+ }
+
+ public static int getLiveMapHintShowCount() {
+ return getInt(R.string.pref_livemaphintshowcount, 0);
+ }
+
+ public static void setLiveMapHintShowCount(final int showCount) {
+ putInt(R.string.pref_livemaphintshowcount, showCount);
+ }
+
+ public static boolean isDbOnSDCard() {
+ return getBoolean(R.string.pref_dbonsdcard, false);
+ }
+
+ public static void setDbOnSDCard(final boolean dbOnSDCard) {
+ putBoolean(R.string.pref_dbonsdcard, dbOnSDCard);
+ }
+
+ public static String getGpxExportDir() {
+ return getString(R.string.pref_gpxExportDir,
+ Environment.getExternalStorageDirectory().getPath() + "/gpx");
+ }
+
+ public static String getGpxImportDir() {
+ return getString(R.string.pref_gpxImportDir,
+ Environment.getExternalStorageDirectory().getPath() + "/gpx");
+ }
+
+ public static boolean getShareAfterExport() {
+ return getBoolean(R.string.pref_shareafterexport, true);
+ }
+
+ public static void setShareAfterExport(final boolean shareAfterExport) {
+ putBoolean(R.string.pref_shareafterexport, shareAfterExport);
+ }
+
+ public static int getTrackableAction() {
+ return getInt(R.string.pref_trackableaction, LogTypeTrackable.RETRIEVED_IT.id);
+ }
+
+ public static void setTrackableAction(final int trackableAction) {
+ putInt(R.string.pref_trackableaction, trackableAction);
+ }
+
+ private static String getCustomRenderThemeBaseFolder() {
+ return getString(R.string.pref_renderthemepath, "");
+ }
+
+ public static String getCustomRenderThemeFilePath() {
+ return getString(R.string.pref_renderthemefile, "");
+ }
+
+ public static void setCustomRenderThemeFile(final String customRenderThemeFile) {
+ putString(R.string.pref_renderthemefile, customRenderThemeFile);
+ }
+
+ public static File[] getMapThemeFiles() {
+ final File directory = new File(getCustomRenderThemeBaseFolder());
+ final List<File> result = new ArrayList<>();
+ FileUtils.listDir(result, directory, new ExtensionsBasedFileSelector(new String[] { "xml" }), null);
+
+ return result.toArray(new File[result.size()]);
+ }
+
+ private static class ExtensionsBasedFileSelector implements FileSelector {
+ private final String[] extensions;
+ public ExtensionsBasedFileSelector(final String[] extensions) {
+ this.extensions = extensions;
+ }
+ @Override
+ public boolean isSelected(final File file) {
+ final String filename = file.getName();
+ for (final String ext : extensions) {
+ if (StringUtils.endsWithIgnoreCase(filename, ext)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ @Override
+ public boolean shouldEnd() {
+ return false;
+ }
+ }
+
+ /**
+ * @return true if plain text log wanted
+ */
+ public static boolean getPlainLogs() {
+ return getBoolean(R.string.pref_plainLogs, false);
+ }
+
+ /**
+ * Force set the plain text log preference
+ *
+ * @param plainLogs
+ * wanted or not
+ */
+ public static void setPlainLogs(final boolean plainLogs) {
+ putBoolean(R.string.pref_plainLogs, plainLogs);
+ }
+
+ public static boolean getUseNativeUa() {
+ return getBoolean(R.string.pref_nativeUa, false);
+ }
+
+ @NonNull
+ public static String getCacheTwitterMessage() {
+ return StringUtils.defaultString(getString(R.string.pref_twitter_cache_message, "I found [NAME] ([URL])."));
+ }
+
+ @NonNull
+ public static String getTrackableTwitterMessage() {
+ return StringUtils.defaultString(getString(R.string.pref_twitter_trackable_message, "I touched [NAME] ([URL])."));
+ }
+
+ public static int getLogImageScale() {
+ return getInt(R.string.pref_logImageScale, -1);
+ }
+
+ public static void setLogImageScale(final int scale) {
+ putInt(R.string.pref_logImageScale, scale);
+ }
+
+ public static void setExcludeMine(final boolean exclude) {
+ putBoolean(R.string.pref_excludemine, exclude);
+ }
+
+ static void setLogin(final String username, final String password) {
+ if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
+ // erase username and password
+ remove(R.string.pref_username);
+ remove(R.string.pref_password);
+ return;
+ }
+ // save username and password
+ putString(R.string.pref_username, username);
+ putString(R.string.pref_password, password);
+ }
+
+ public static long getFieldnoteExportDate() {
+ return getLong(R.string.pref_fieldNoteExportDate, 0);
+ }
+
+ /**
+ * Remember date of last field note export.
+ */
+ public static void setFieldnoteExportDate(final long date) {
+ putLong(R.string.pref_fieldNoteExportDate, date);
+ }
+
+ public static boolean isUseNavigationApp(final NavigationAppsEnum navApp) {
+ return getBoolean(navApp.preferenceKey, true);
+ }
+
+ /**
+ * Remember the state of the "Upload" checkbox in the field notes export dialog.
+ */
+ public static void setFieldNoteExportUpload(final boolean upload) {
+ putBoolean(R.string.pref_fieldNoteExportUpload, upload);
+ }
+
+ public static boolean getFieldNoteExportUpload() {
+ return getBoolean(R.string.pref_fieldNoteExportUpload, true);
+ }
+
+ /**
+ * Remember the state of the "Only new" checkbox in the field notes export dialog.
+ */
+ public static void setFieldNoteExportOnlyNew(final boolean onlyNew) {
+ putBoolean(R.string.pref_fieldNoteExportOnlyNew, onlyNew);
+ }
+
+ public static boolean getFieldNoteExportOnlyNew() {
+ return getBoolean(R.string.pref_fieldNoteExportOnlyNew, false);
+ }
+
+ public static String getECIconSet() {
+ return getString(R.string.pref_ec_icons, "1");
+ }
+
+ /* Store last checksum of changelog for changelog display */
+ public static long getLastChangelogChecksum() {
+ return getLong(R.string.pref_changelog_last_checksum, 0);
+ }
+
+ public static void setLastChangelogChecksum(final long checksum) {
+ putLong(R.string.pref_changelog_last_checksum, checksum);
+ }
+
+ public static List<String> getLastOpenedCaches() {
+ final List<String> history = Arrays.asList(StringUtils.split(getString(R.string.pref_caches_history, StringUtils.EMPTY), HISTORY_SEPARATOR));
+ return history.subList(0, Math.min(HISTORY_SIZE, history.size()));
+ }
+
+ public static void addCacheToHistory(@NonNull final String geocode) {
+ final List<String> history = new ArrayList<>(getLastOpenedCaches());
+ // bring entry to front, if it already existed
+ history.remove(geocode);
+ history.add(0, geocode);
+ putString(R.string.pref_caches_history, StringUtils.join(history, HISTORY_SEPARATOR));
+ }
+
+ public static boolean useHardwareAcceleration() {
+ return getBoolean(R.string.pref_hardware_acceleration, !HW_ACCEL_DISABLED_BY_DEFAULT);
+ }
+
+ static void setUseHardwareAcceleration(final boolean useHardwareAcceleration) {
+ putBoolean(R.string.pref_hardware_acceleration, useHardwareAcceleration);
+ }
+
+ public static String getLastCacheLog() {
+ return getString(R.string.pref_last_cache_log, StringUtils.EMPTY);
+ }
+
+ public static void setLastCacheLog(final String log) {
+ putString(R.string.pref_last_cache_log, log);
+ }
+
+ public static String getLastTrackableLog() {
+ return getString(R.string.pref_last_trackable_log, StringUtils.EMPTY);
+ }
+
+ public static void setLastTrackableLog(final String log) {
+ putString(R.string.pref_last_trackable_log, log);
+ }
+
+ @Nullable
+ public static String getHomeLocation() {
+ return getString(R.string.pref_home_location, null);
+ }
+
+ public static void setHomeLocation(@NonNull final String homeLocation) {
+ putString(R.string.pref_home_location, homeLocation);
+ }
+
+ public static void setForceOrientationSensor(final boolean forceOrientationSensor) {
+ putBoolean(R.string.pref_force_orientation_sensor, forceOrientationSensor);
+ }
+
+ public static boolean useOrientationSensor(final Context context) {
+ return OrientationProvider.hasOrientationSensor(context) && (getBoolean(R.string.pref_force_orientation_sensor, false) || !RotationProvider.hasRotationSensor(context));
+ }
+}
diff --git a/main/src/cgeo/geocaching/utils/CryptUtils.java b/main/src/cgeo/geocaching/utils/CryptUtils.java index 4aec509..6ef8c25 100644 --- a/main/src/cgeo/geocaching/utils/CryptUtils.java +++ b/main/src/cgeo/geocaching/utils/CryptUtils.java @@ -1,153 +1,156 @@ -package cgeo.geocaching.utils; - -import org.apache.commons.lang3.CharEncoding; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.jdt.annotation.NonNull; - -import android.text.Spannable; -import android.text.SpannableStringBuilder; - -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -public final class CryptUtils { - - private CryptUtils() { - // utility class - } - - private static final byte[] EMPTY = {}; - private static final char[] BASE64MAP1 = new char[64]; - private static final byte[] BASE64MAP2 = new byte[128]; - - static { - int i = 0; - for (char c = 'A'; c <= 'Z'; c++) { - BASE64MAP1[i++] = c; - } - for (char c = 'a'; c <= 'z'; c++) { - BASE64MAP1[i++] = c; - } - for (char c = '0'; c <= '9'; c++) { - BASE64MAP1[i++] = c; - } - BASE64MAP1[i++] = '+'; - BASE64MAP1[i++] = '/'; - - for (i = 0; i < BASE64MAP2.length; i++) { - BASE64MAP2[i] = -1; - } - for (i = 0; i < 64; i++) { - BASE64MAP2[BASE64MAP1[i]] = (byte) i; - } - } - - private static class Rot13Encryption { - private boolean plaintext = false; - - char getNextEncryptedCharacter(final char c) { - int result = c; - if (result == '[') { - plaintext = true; - } else if (result == ']') { - plaintext = false; - } else if (!plaintext) { - final int capitalized = result & 32; - result &= ~capitalized; - result = ((result >= 'A') && (result <= 'Z') ? ((result - 'A' + 13) % 26 + 'A') : result) - | capitalized; - } - return (char) result; - } - } - - @NonNull - public static String rot13(final String text) { - if (text == null) { - return StringUtils.EMPTY; - } - final StringBuilder result = new StringBuilder(); - final Rot13Encryption rot13 = new Rot13Encryption(); - - final int length = text.length(); - for (int index = 0; index < length; index++) { - final char c = text.charAt(index); - result.append(rot13.getNextEncryptedCharacter(c)); - } - return result.toString(); - } - - public static String md5(final String text) { - try { - final MessageDigest digest = MessageDigest.getInstance("MD5"); - digest.update(text.getBytes(CharEncoding.UTF_8), 0, text.length()); - return new BigInteger(1, digest.digest()).toString(16); - } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { - Log.e("CryptUtils.md5", e); - } - - return StringUtils.EMPTY; - } - - public static byte[] hashHmac(final String text, final String salt) { - - try { - final SecretKeySpec secretKeySpec = new SecretKeySpec(salt.getBytes(CharEncoding.UTF_8), "HmacSHA1"); - final Mac mac = Mac.getInstance("HmacSHA1"); - mac.init(secretKeySpec); - return mac.doFinal(text.getBytes(CharEncoding.UTF_8)); - } catch (GeneralSecurityException | UnsupportedEncodingException e) { - Log.e("CryptUtils.hashHmac", e); - return EMPTY; - } - } - - public static CharSequence rot13(final Spannable span) { - // I needed to re-implement the rot13(String) encryption here because we must work on - // a SpannableStringBuilder instead of the pure text and we must replace each character inline. - // Otherwise we loose all the images, colors and so on... - final SpannableStringBuilder buffer = new SpannableStringBuilder(span); - final Rot13Encryption rot13 = new Rot13Encryption(); - - final int length = span.length(); - for (int index = 0; index < length; index++) { - final char c = span.charAt(index); - buffer.replace(index, index + 1, String.valueOf(rot13.getNextEncryptedCharacter(c))); - } - return buffer; - } - - public static String base64Encode(final byte[] in) { - final int iLen = in.length; - final int oDataLen = (iLen * 4 + 2) / 3; // output length without padding - final int oLen = ((iLen + 2) / 3) * 4; // output length including padding - final char[] out = new char[oLen]; - int ip = 0; - int op = 0; - - while (ip < iLen) { - final int i0 = in[ip++] & 0xff; - final int i1 = ip < iLen ? in[ip++] & 0xff : 0; - final int i2 = ip < iLen ? in[ip++] & 0xff : 0; - final int o0 = i0 >>> 2; - final int o1 = ((i0 & 3) << 4) | (i1 >>> 4); - final int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); - final int o3 = i2 & 0x3F; - out[op++] = BASE64MAP1[o0]; - out[op++] = BASE64MAP1[o1]; - out[op] = op < oDataLen ? BASE64MAP1[o2] : '='; - op++; - out[op] = op < oDataLen ? BASE64MAP1[o3] : '='; - op++; - } - - return new String(out); - } - -} +package cgeo.geocaching.utils;
+
+import org.apache.commons.lang3.CharEncoding;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+public final class CryptUtils {
+
+ private CryptUtils() {
+ // utility class
+ }
+
+ private static final byte[] EMPTY = {};
+ private static final char[] BASE64MAP1 = new char[64];
+ private static final byte[] BASE64MAP2 = new byte[128];
+
+ static {
+ int i = 0;
+ for (char c = 'A'; c <= 'Z'; c++) {
+ BASE64MAP1[i++] = c;
+ }
+ for (char c = 'a'; c <= 'z'; c++) {
+ BASE64MAP1[i++] = c;
+ }
+ for (char c = '0'; c <= '9'; c++) {
+ BASE64MAP1[i++] = c;
+ }
+ BASE64MAP1[i++] = '+';
+ BASE64MAP1[i++] = '/';
+
+ for (i = 0; i < BASE64MAP2.length; i++) {
+ BASE64MAP2[i] = -1;
+ }
+ for (i = 0; i < 64; i++) {
+ BASE64MAP2[BASE64MAP1[i]] = (byte) i;
+ }
+ }
+
+ private static class Rot13Encryption {
+ private boolean plaintext = false;
+
+ char getNextEncryptedCharacter(final char c) {
+ int result = c;
+ if (result == '[') {
+ plaintext = true;
+ } else if (result == ']') {
+ plaintext = false;
+ } else if (!plaintext) {
+ final int capitalized = result & 32;
+ result &= ~capitalized;
+ result = ((result >= 'A') && (result <= 'Z') ? ((result - 'A' + 13) % 26 + 'A') : result)
+ | capitalized;
+ }
+ return (char) result;
+ }
+ }
+
+ @NonNull
+ public static String rot13(final String text) {
+ if (text == null) {
+ return StringUtils.EMPTY;
+ }
+ final StringBuilder result = new StringBuilder();
+ final Rot13Encryption rot13 = new Rot13Encryption();
+
+ final int length = text.length();
+ for (int index = 0; index < length; index++) {
+ final char c = text.charAt(index);
+ result.append(rot13.getNextEncryptedCharacter(c));
+ }
+ return result.toString();
+ }
+
+ @NonNull
+ public static String md5(final String text) {
+ try {
+ final MessageDigest digest = MessageDigest.getInstance("MD5");
+ digest.update(text.getBytes(CharEncoding.UTF_8), 0, text.length());
+ return new BigInteger(1, digest.digest()).toString(16);
+ } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ Log.e("CryptUtils.md5", e);
+ }
+
+ return StringUtils.EMPTY;
+ }
+
+ @NonNull
+ public static byte[] hashHmac(final String text, final String salt) {
+ try {
+ final SecretKeySpec secretKeySpec = new SecretKeySpec(salt.getBytes(CharEncoding.UTF_8), "HmacSHA1");
+ final Mac mac = Mac.getInstance("HmacSHA1");
+ mac.init(secretKeySpec);
+ return mac.doFinal(text.getBytes(CharEncoding.UTF_8));
+ } catch (GeneralSecurityException | UnsupportedEncodingException e) {
+ Log.e("CryptUtils.hashHmac", e);
+ return EMPTY;
+ }
+ }
+
+ @NonNull
+ public static CharSequence rot13(final Spannable span) {
+ // I needed to re-implement the rot13(String) encryption here because we must work on
+ // a SpannableStringBuilder instead of the pure text and we must replace each character inline.
+ // Otherwise we loose all the images, colors and so on...
+ final SpannableStringBuilder buffer = new SpannableStringBuilder(span);
+ final Rot13Encryption rot13 = new Rot13Encryption();
+
+ final int length = span.length();
+ for (int index = 0; index < length; index++) {
+ final char c = span.charAt(index);
+ buffer.replace(index, index + 1, String.valueOf(rot13.getNextEncryptedCharacter(c)));
+ }
+ return buffer;
+ }
+
+ @NonNull
+ public static String base64Encode(final byte[] in) {
+ final int iLen = in.length;
+ final int oDataLen = (iLen * 4 + 2) / 3; // output length without padding
+ final int oLen = ((iLen + 2) / 3) * 4; // output length including padding
+ final char[] out = new char[oLen];
+ int ip = 0;
+ int op = 0;
+
+ while (ip < iLen) {
+ final int i0 = in[ip++] & 0xff;
+ final int i1 = ip < iLen ? in[ip++] & 0xff : 0;
+ final int i2 = ip < iLen ? in[ip++] & 0xff : 0;
+ final int o0 = i0 >>> 2;
+ final int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
+ final int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
+ final int o3 = i2 & 0x3F;
+ out[op++] = BASE64MAP1[o0];
+ out[op++] = BASE64MAP1[o1];
+ out[op] = op < oDataLen ? BASE64MAP1[o2] : '=';
+ op++;
+ out[op] = op < oDataLen ? BASE64MAP1[o3] : '=';
+ op++;
+ }
+
+ return new String(out);
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/utils/EnvironmentUtils.java b/main/src/cgeo/geocaching/utils/EnvironmentUtils.java index 90b4c5d..680cc46 100644 --- a/main/src/cgeo/geocaching/utils/EnvironmentUtils.java +++ b/main/src/cgeo/geocaching/utils/EnvironmentUtils.java @@ -1,28 +1,30 @@ -package cgeo.geocaching.utils; - -import org.apache.commons.lang3.StringUtils; - -import android.os.Environment; - -public class EnvironmentUtils { - private EnvironmentUtils() { - // utility class - } - - /** - * Same as {@link Environment#getExternalStorageState()} but more stable. We have seen null pointers here, probably - * when there are issues in the underlying mount. - */ - public static String getExternalStorageState() { - try { - return Environment.getExternalStorageState(); - } catch (final NullPointerException e) { - Log.w("Could not get external storage state", e); - } - return StringUtils.EMPTY; - } - - public static boolean isExternalStorageAvailable() { - return EnvironmentUtils.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); - } -} +package cgeo.geocaching.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+
+import android.os.Environment;
+
+public class EnvironmentUtils {
+ private EnvironmentUtils() {
+ // utility class
+ }
+
+ /**
+ * Same as {@link Environment#getExternalStorageState()} but more stable. We have seen null pointers here, probably
+ * when there are issues in the underlying mount.
+ */
+ @NonNull
+ public static String getExternalStorageState() {
+ try {
+ return Environment.getExternalStorageState();
+ } catch (final NullPointerException e) {
+ Log.w("Could not get external storage state", e);
+ }
+ return StringUtils.EMPTY;
+ }
+
+ public static boolean isExternalStorageAvailable() {
+ return EnvironmentUtils.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
+ }
+}
diff --git a/main/src/cgeo/geocaching/utils/FileUtils.java b/main/src/cgeo/geocaching/utils/FileUtils.java index cae94e4..d26b416 100644 --- a/main/src/cgeo/geocaching/utils/FileUtils.java +++ b/main/src/cgeo/geocaching/utils/FileUtils.java @@ -1,212 +1,215 @@ -package cgeo.geocaching.utils; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.CharEncoding; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.jdt.annotation.NonNull; - -import android.os.Handler; -import android.os.Message; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.List; - -/** - * Utility class for files - * - */ -public final class FileUtils { - - private static final int MAX_DIRECTORY_SCAN_DEPTH = 30; - private static final String FILE_PROTOCOL = "file://"; - - private FileUtils() { - // utility class - } - - public static void listDir(final List<File> result, final File directory, final FileSelector chooser, final Handler feedBackHandler) { - listDirInternally(result, directory, chooser, feedBackHandler, 0); - } - - private static void listDirInternally(final List<File> result, final File directory, final FileSelector chooser, final Handler feedBackHandler, final int depths) { - if (directory == null || !directory.isDirectory() || !directory.canRead() - || result == null - || chooser == null) { - return; - } - - final File[] files = directory.listFiles(); - - if (ArrayUtils.isNotEmpty(files)) { - for (final File file : files) { - if (chooser.shouldEnd()) { - return; - } - if (!file.canRead()) { - continue; - } - String name = file.getName(); - if (file.isFile()) { - if (chooser.isSelected(file)) { - result.add(file); // add file to list - } - } else if (file.isDirectory()) { - if (name.charAt(0) == '.') { - continue; // skip hidden directories - } - if (name.length() > 16) { - name = name.substring(0, 14) + '…'; - } - if (feedBackHandler != null) { - feedBackHandler.sendMessage(Message.obtain(feedBackHandler, 0, name)); - } - - if (depths < MAX_DIRECTORY_SCAN_DEPTH) { - listDirInternally(result, file, chooser, feedBackHandler, depths + 1); // go deeper - } - } - } - } - } - - public static boolean deleteDirectory(@NonNull final File dir) { - final File[] files = dir.listFiles(); - - // Although we are called on an existing directory, it might have been removed concurrently - // in the meantime, for example by the user or by another cleanup task. - if (files != null) { - for (final File file : files) { - if (file.isDirectory()) { - deleteDirectory(file); - } else { - delete(file); - } - } - } - - return delete(dir); - } - - public interface FileSelector { - boolean isSelected(File file); - boolean shouldEnd(); - } - - /** - * Create a unique non existing file named like the given file name. If a file with the given name already exists, - * add a number as suffix to the file name.<br> - * Example: For the file name "file.ext" this will return the first file of the list - * <ul> - * <li>file.ext</li> - * <li>file_2.ext</li> - * <li>file_3.ext</li> - * </ul> - * which does not yet exist. - */ - public static File getUniqueNamedFile(final File file) { - if (!file.exists()) { - return file; - } - final String baseNameAndPath = file.getPath(); - final String prefix = StringUtils.substringBeforeLast(baseNameAndPath, ".") + "_"; - final String extension = "." + StringUtils.substringAfterLast(baseNameAndPath, "."); - for (int i = 2; i < Integer.MAX_VALUE; i++) { - final File numbered = new File(prefix + i + extension); - if (!numbered.exists()) { - return numbered; - } - } - throw new IllegalStateException("Unable to generate a non-existing file name"); - } - - /** - * This usage of this method indicates that the return value of File.delete() can safely be ignored. - */ - public static void deleteIgnoringFailure(final File file) { - final boolean success = file.delete() || !file.exists(); - if (!success) { - Log.i("Could not delete " + file.getAbsolutePath()); - } - } - - /** - * Deletes a file and logs deletion failures. - * - * @return <code> true</code> if this file was deleted, <code>false</code> otherwise. - */ - public static boolean delete(final File file) { - final boolean success = file.delete() || !file.exists(); - if (!success) { - Log.e("Could not delete " + file.getAbsolutePath()); - } - return success; - } - - /** - * Creates the directory named by the given file, creating any missing parent directories in the process. - * - * @return <code>true</code> if the directory was created, <code>false</code> on failure or if the directory already - * existed. - */ - public static boolean mkdirs(final File file) { - final boolean success = file.mkdirs() || file.isDirectory(); // mkdirs returns false on existing directories - if (!success) { - Log.e("Could not make directories " + file.getAbsolutePath()); - } - return success; - } - - public static boolean writeFileUTF16(final File file, final String content) { - // TODO: replace by some apache.commons IOUtils or FileUtils code - Writer fileWriter = null; - BufferedOutputStream buffer = null; - try { - final OutputStream os = new FileOutputStream(file); - buffer = new BufferedOutputStream(os); - fileWriter = new OutputStreamWriter(buffer, CharEncoding.UTF_16); - fileWriter.write(content); - } catch (final IOException e) { - Log.e("FieldnoteExport.ExportTask export", e); - return false; - } finally { - IOUtils.closeQuietly(fileWriter); - IOUtils.closeQuietly(buffer); - } - return true; - } - - /** - * Check if the URL represents a file on the local file system. - * - * @return <tt>true</tt> if the URL scheme is <tt>file</tt>, <tt>false</tt> otherwise - */ - public static boolean isFileUrl(final String url) { - return StringUtils.startsWith(url, FILE_PROTOCOL); - } - - /** - * Build an URL from a file name. - * - * @param file a local file name - * @return an URL with the <tt>file</tt> scheme - */ - public static String fileToUrl(final File file) { - return FILE_PROTOCOL + file.getAbsolutePath(); - } - - /** - * Local file name when {@link #isFileUrl(String)} is <tt>true</tt>. - * - * @return the local file - */ - public static File urlToFile(final String url) { - return new File(StringUtils.substring(url, FILE_PROTOCOL.length())); - } -} +package cgeo.geocaching.utils;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.CharEncoding;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+
+import android.os.Handler;
+import android.os.Message;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.List;
+
+/**
+ * Utility class for files
+ *
+ */
+public final class FileUtils {
+
+ private static final int MAX_DIRECTORY_SCAN_DEPTH = 30;
+ private static final String FILE_PROTOCOL = "file://";
+
+ private FileUtils() {
+ // utility class
+ }
+
+ public static void listDir(final List<File> result, final File directory, final FileSelector chooser, final Handler feedBackHandler) {
+ listDirInternally(result, directory, chooser, feedBackHandler, 0);
+ }
+
+ private static void listDirInternally(final List<File> result, final File directory, final FileSelector chooser, final Handler feedBackHandler, final int depths) {
+ if (directory == null || !directory.isDirectory() || !directory.canRead()
+ || result == null
+ || chooser == null) {
+ return;
+ }
+
+ final File[] files = directory.listFiles();
+
+ if (ArrayUtils.isNotEmpty(files)) {
+ for (final File file : files) {
+ if (chooser.shouldEnd()) {
+ return;
+ }
+ if (!file.canRead()) {
+ continue;
+ }
+ String name = file.getName();
+ if (file.isFile()) {
+ if (chooser.isSelected(file)) {
+ result.add(file); // add file to list
+ }
+ } else if (file.isDirectory()) {
+ if (name.charAt(0) == '.') {
+ continue; // skip hidden directories
+ }
+ if (name.length() > 16) {
+ name = name.substring(0, 14) + '…';
+ }
+ if (feedBackHandler != null) {
+ feedBackHandler.sendMessage(Message.obtain(feedBackHandler, 0, name));
+ }
+
+ if (depths < MAX_DIRECTORY_SCAN_DEPTH) {
+ listDirInternally(result, file, chooser, feedBackHandler, depths + 1); // go deeper
+ }
+ }
+ }
+ }
+ }
+
+ public static boolean deleteDirectory(@NonNull final File dir) {
+ final File[] files = dir.listFiles();
+
+ // Although we are called on an existing directory, it might have been removed concurrently
+ // in the meantime, for example by the user or by another cleanup task.
+ if (files != null) {
+ for (final File file : files) {
+ if (file.isDirectory()) {
+ deleteDirectory(file);
+ } else {
+ delete(file);
+ }
+ }
+ }
+
+ return delete(dir);
+ }
+
+ public interface FileSelector {
+ boolean isSelected(File file);
+ boolean shouldEnd();
+ }
+
+ /**
+ * Create a unique non existing file named like the given file name. If a file with the given name already exists,
+ * add a number as suffix to the file name.<br>
+ * Example: For the file name "file.ext" this will return the first file of the list
+ * <ul>
+ * <li>file.ext</li>
+ * <li>file_2.ext</li>
+ * <li>file_3.ext</li>
+ * </ul>
+ * which does not yet exist.
+ */
+ @NonNull
+ public static File getUniqueNamedFile(final File file) {
+ if (!file.exists()) {
+ return file;
+ }
+ final String baseNameAndPath = file.getPath();
+ final String prefix = StringUtils.substringBeforeLast(baseNameAndPath, ".") + "_";
+ final String extension = "." + StringUtils.substringAfterLast(baseNameAndPath, ".");
+ for (int i = 2; i < Integer.MAX_VALUE; i++) {
+ final File numbered = new File(prefix + i + extension);
+ if (!numbered.exists()) {
+ return numbered;
+ }
+ }
+ throw new IllegalStateException("Unable to generate a non-existing file name");
+ }
+
+ /**
+ * This usage of this method indicates that the return value of File.delete() can safely be ignored.
+ */
+ public static void deleteIgnoringFailure(final File file) {
+ final boolean success = file.delete() || !file.exists();
+ if (!success) {
+ Log.i("Could not delete " + file.getAbsolutePath());
+ }
+ }
+
+ /**
+ * Deletes a file and logs deletion failures.
+ *
+ * @return <code> true</code> if this file was deleted, <code>false</code> otherwise.
+ */
+ public static boolean delete(final File file) {
+ final boolean success = file.delete() || !file.exists();
+ if (!success) {
+ Log.e("Could not delete " + file.getAbsolutePath());
+ }
+ return success;
+ }
+
+ /**
+ * Creates the directory named by the given file, creating any missing parent directories in the process.
+ *
+ * @return <code>true</code> if the directory was created, <code>false</code> on failure or if the directory already
+ * existed.
+ */
+ public static boolean mkdirs(final File file) {
+ final boolean success = file.mkdirs() || file.isDirectory(); // mkdirs returns false on existing directories
+ if (!success) {
+ Log.e("Could not make directories " + file.getAbsolutePath());
+ }
+ return success;
+ }
+
+ public static boolean writeFileUTF16(final File file, final String content) {
+ // TODO: replace by some apache.commons IOUtils or FileUtils code
+ Writer fileWriter = null;
+ BufferedOutputStream buffer = null;
+ try {
+ final OutputStream os = new FileOutputStream(file);
+ buffer = new BufferedOutputStream(os);
+ fileWriter = new OutputStreamWriter(buffer, CharEncoding.UTF_16);
+ fileWriter.write(content);
+ } catch (final IOException e) {
+ Log.e("FieldnoteExport.ExportTask export", e);
+ return false;
+ } finally {
+ IOUtils.closeQuietly(fileWriter);
+ IOUtils.closeQuietly(buffer);
+ }
+ return true;
+ }
+
+ /**
+ * Check if the URL represents a file on the local file system.
+ *
+ * @return <tt>true</tt> if the URL scheme is <tt>file</tt>, <tt>false</tt> otherwise
+ */
+ public static boolean isFileUrl(final String url) {
+ return StringUtils.startsWith(url, FILE_PROTOCOL);
+ }
+
+ /**
+ * Build an URL from a file name.
+ *
+ * @param file a local file name
+ * @return an URL with the <tt>file</tt> scheme
+ */
+ @NonNull
+ public static String fileToUrl(final File file) {
+ return FILE_PROTOCOL + file.getAbsolutePath();
+ }
+
+ /**
+ * Local file name when {@link #isFileUrl(String)} is <tt>true</tt>.
+ *
+ * @return the local file
+ */
+ @NonNull
+ public static File urlToFile(final String url) {
+ return new File(StringUtils.substring(url, FILE_PROTOCOL.length()));
+ }
+}
diff --git a/main/src/cgeo/geocaching/utils/Formatter.java b/main/src/cgeo/geocaching/utils/Formatter.java index ed9a72c..afca300 100644 --- a/main/src/cgeo/geocaching/utils/Formatter.java +++ b/main/src/cgeo/geocaching/utils/Formatter.java @@ -1,238 +1,254 @@ -package cgeo.geocaching.utils; - -import cgeo.geocaching.CgeoApplication; -import cgeo.geocaching.Geocache; -import cgeo.geocaching.R; -import cgeo.geocaching.Waypoint; -import cgeo.geocaching.enumerations.CacheSize; -import cgeo.geocaching.enumerations.WaypointType; - -import org.apache.commons.lang3.StringUtils; - -import android.content.Context; -import android.text.format.DateUtils; - -import java.text.DateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -public final class Formatter { - - /** Text separator used for formatting texts */ - public static final String SEPARATOR = " · "; - - private static final Context context = CgeoApplication.getInstance().getBaseContext(); - - private Formatter() { - // Utility class - } - - /** - * Generate a time string according to system-wide settings (locale, 12/24 hour) - * such as "13:24". - * - * @param date - * milliseconds since the epoch - * @return the formatted string - */ - public static String formatTime(final long date) { - return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_TIME); - } - - /** - * Generate a date string according to system-wide settings (locale, date format) - * such as "20 December" or "20 December 2010". The year will only be included when necessary. - * - * @param date - * milliseconds since the epoch - * @return the formatted string - */ - public static String formatDate(final long date) { - return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE); - } - - /** - * Generate a date string according to system-wide settings (locale, date format) - * such as "20 December 2010". The year will always be included, making it suitable - * to generate long-lived log entries. - * - * @param date - * milliseconds since the epoch - * @return the formatted string - */ - public static String formatFullDate(final long date) { - return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_YEAR); - } - - /** - * Generate a numeric date string according to system-wide settings (locale, date format) - * such as "10/20/2010". - * - * @param date - * milliseconds since the epoch - * @return the formatted string - */ - public static String formatShortDate(final long date) { - final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context); - return dateFormat.format(date); - } - - private static String formatShortDateIncludingWeekday(final long time) { - return DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY) + ", " + formatShortDate(time); - } - - /** - * Generate a numeric date string according to system-wide settings (locale, date format) - * such as "10/20/2010". Today and yesterday will be presented as strings "today" and "yesterday". - * - * @param date - * milliseconds since the epoch - * @return the formatted string - */ - public static String formatShortDateVerbally(final long date) { - final int diff = CalendarUtils.daysSince(date); - switch (diff) { - case 0: - return CgeoApplication.getInstance().getString(R.string.log_today); - case 1: - return CgeoApplication.getInstance().getString(R.string.log_yesterday); - default: - return formatShortDate(date); - } - } - - /** - * Generate a numeric date and time string according to system-wide settings (locale, - * date format) such as "7 sept. at 12:35". - * - * @param date - * milliseconds since the epoch - * @return the formatted string - */ - public static String formatShortDateTime(final long date) { - return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL); - } - - /** - * Generate a numeric date and time string according to system-wide settings (locale, - * date format) such as "7 september at 12:35". - * - * @param date - * milliseconds since the epoch - * @return the formatted string - */ - public static String formatDateTime(final long date) { - return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME); - } - - public static String formatCacheInfoLong(final Geocache cache) { - final List<String> infos = new ArrayList<>(); - if (StringUtils.isNotBlank(cache.getGeocode())) { - infos.add(cache.getGeocode()); - } - - addShortInfos(cache, infos); - - if (cache.isPremiumMembersOnly()) { - infos.add(CgeoApplication.getInstance().getString(R.string.cache_premium)); - } - return StringUtils.join(infos, SEPARATOR); - } - - public static String formatCacheInfoShort(final Geocache cache) { - final List<String> infos = new ArrayList<>(); - addShortInfos(cache, infos); - return StringUtils.join(infos, SEPARATOR); - } - - private static void addShortInfos(final Geocache cache, final List<String> infos) { - if (cache.hasDifficulty()) { - infos.add("D " + formatDT(cache.getDifficulty())); - } - if (cache.hasTerrain()) { - infos.add("T " + formatDT(cache.getTerrain())); - } - - // don't show "not chosen" for events and virtuals, that should be the normal case - if (cache.getSize() != CacheSize.UNKNOWN && cache.showSize()) { - infos.add(cache.getSize().getL10n()); - } else if (cache.isEventCache()) { - final Date hiddenDate = cache.getHiddenDate(); - if (hiddenDate != null) { - infos.add(formatShortDateIncludingWeekday(hiddenDate.getTime())); - } - } - } - - private static String formatDT(final float value) { - return String.format(Locale.getDefault(), "%.1f", value); - } - - public static String formatCacheInfoHistory(final Geocache cache) { - final List<String> infos = new ArrayList<>(3); - infos.add(StringUtils.upperCase(cache.getGeocode())); - infos.add(formatDate(cache.getVisitedDate())); - infos.add(formatTime(cache.getVisitedDate())); - return StringUtils.join(infos, SEPARATOR); - } - - public static String formatWaypointInfo(final Waypoint waypoint) { - final List<String> infos = new ArrayList<>(3); - final WaypointType waypointType = waypoint.getWaypointType(); - if (waypointType != WaypointType.OWN && waypointType != null) { - infos.add(waypointType.getL10n()); - } - if (Waypoint.PREFIX_OWN.equalsIgnoreCase(waypoint.getPrefix())) { - infos.add(CgeoApplication.getInstance().getString(R.string.waypoint_custom)); - } else { - if (StringUtils.isNotBlank(waypoint.getPrefix())) { - infos.add(waypoint.getPrefix()); - } - if (StringUtils.isNotBlank(waypoint.getLookup())) { - infos.add(waypoint.getLookup()); - } - } - return StringUtils.join(infos, SEPARATOR); - } - - public static String formatDaysAgo(final long date) { - final int days = CalendarUtils.daysSince(date); - switch (days) { - case 0: - return CgeoApplication.getInstance().getString(R.string.log_today); - case 1: - return CgeoApplication.getInstance().getString(R.string.log_yesterday); - default: - return CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.days_ago, days, days); - } - } - - /** - * Formatting of the hidden date of a cache - * - * @return {@code null} or hidden date of the cache (or event date of the cache) in human readable format - */ - public static String formatHiddenDate(final Geocache cache) { - final Date hiddenDate = cache.getHiddenDate(); - if (hiddenDate == null) { - return null; - } - final long time = hiddenDate.getTime(); - if (time <= 0) { - return null; - } - String dateString = formatFullDate(time); - if (cache.isEventCache()) { - dateString = DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY) + ", " + dateString; - } - return dateString; - } - - public static String formatMapSubtitle(final Geocache cache) { - return "D " + formatDT(cache.getDifficulty()) + SEPARATOR + "T " + formatDT(cache.getTerrain()) + SEPARATOR + cache.getGeocode(); - } - -} +package cgeo.geocaching.utils;
+
+import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.Geocache;
+import cgeo.geocaching.R;
+import cgeo.geocaching.Waypoint;
+import cgeo.geocaching.enumerations.CacheSize;
+import cgeo.geocaching.enumerations.WaypointType;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+public final class Formatter {
+
+ /** Text separator used for formatting texts */
+ public static final String SEPARATOR = " · ";
+
+ private static final Context context = CgeoApplication.getInstance().getBaseContext();
+
+ private Formatter() {
+ // Utility class
+ }
+
+ /**
+ * Generate a time string according to system-wide settings (locale, 12/24 hour)
+ * such as "13:24".
+ *
+ * @param date
+ * milliseconds since the epoch
+ * @return the formatted string
+ */
+ @NonNull
+ public static String formatTime(final long date) {
+ return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_TIME);
+ }
+
+ /**
+ * Generate a date string according to system-wide settings (locale, date format)
+ * such as "20 December" or "20 December 2010". The year will only be included when necessary.
+ *
+ * @param date
+ * milliseconds since the epoch
+ * @return the formatted string
+ */
+ @NonNull
+ public static String formatDate(final long date) {
+ return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE);
+ }
+
+ /**
+ * Generate a date string according to system-wide settings (locale, date format)
+ * such as "20 December 2010". The year will always be included, making it suitable
+ * to generate long-lived log entries.
+ *
+ * @param date
+ * milliseconds since the epoch
+ * @return the formatted string
+ */
+ @NonNull
+ public static String formatFullDate(final long date) {
+ return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_SHOW_YEAR);
+ }
+
+ /**
+ * Generate a numeric date string according to system-wide settings (locale, date format)
+ * such as "10/20/2010".
+ *
+ * @param date
+ * milliseconds since the epoch
+ * @return the formatted string
+ */
+ @NonNull
+ public static String formatShortDate(final long date) {
+ final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context);
+ return dateFormat.format(date);
+ }
+
+ private static String formatShortDateIncludingWeekday(final long time) {
+ return DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY) + ", " + formatShortDate(time);
+ }
+
+ /**
+ * Generate a numeric date string according to system-wide settings (locale, date format)
+ * such as "10/20/2010". Today and yesterday will be presented as strings "today" and "yesterday".
+ *
+ * @param date
+ * milliseconds since the epoch
+ * @return the formatted string
+ */
+ @NonNull
+ public static String formatShortDateVerbally(final long date) {
+ final int diff = CalendarUtils.daysSince(date);
+ switch (diff) {
+ case 0:
+ return CgeoApplication.getInstance().getString(R.string.log_today);
+ case 1:
+ return CgeoApplication.getInstance().getString(R.string.log_yesterday);
+ default:
+ return formatShortDate(date);
+ }
+ }
+
+ /**
+ * Generate a numeric date and time string according to system-wide settings (locale,
+ * date format) such as "7 sept. at 12:35".
+ *
+ * @param date
+ * milliseconds since the epoch
+ * @return the formatted string
+ */
+ @NonNull
+ public static String formatShortDateTime(final long date) {
+ return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL);
+ }
+
+ /**
+ * Generate a numeric date and time string according to system-wide settings (locale,
+ * date format) such as "7 september at 12:35".
+ *
+ * @param date
+ * milliseconds since the epoch
+ * @return the formatted string
+ */
+ @NonNull
+ public static String formatDateTime(final long date) {
+ return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
+ }
+
+ @NonNull
+ public static String formatCacheInfoLong(final Geocache cache) {
+ final List<String> infos = new ArrayList<>();
+ if (StringUtils.isNotBlank(cache.getGeocode())) {
+ infos.add(cache.getGeocode());
+ }
+
+ addShortInfos(cache, infos);
+
+ if (cache.isPremiumMembersOnly()) {
+ infos.add(CgeoApplication.getInstance().getString(R.string.cache_premium));
+ }
+ return StringUtils.join(infos, SEPARATOR);
+ }
+
+ @NonNull
+ public static String formatCacheInfoShort(final Geocache cache) {
+ final List<String> infos = new ArrayList<>();
+ addShortInfos(cache, infos);
+ return StringUtils.join(infos, SEPARATOR);
+ }
+
+ private static void addShortInfos(final Geocache cache, final List<String> infos) {
+ if (cache.hasDifficulty()) {
+ infos.add("D " + formatDT(cache.getDifficulty()));
+ }
+ if (cache.hasTerrain()) {
+ infos.add("T " + formatDT(cache.getTerrain()));
+ }
+
+ // don't show "not chosen" for events and virtuals, that should be the normal case
+ if (cache.getSize() != CacheSize.UNKNOWN && cache.showSize()) {
+ infos.add(cache.getSize().getL10n());
+ } else if (cache.isEventCache()) {
+ final Date hiddenDate = cache.getHiddenDate();
+ if (hiddenDate != null) {
+ infos.add(formatShortDateIncludingWeekday(hiddenDate.getTime()));
+ }
+ }
+ }
+
+ private static String formatDT(final float value) {
+ return String.format(Locale.getDefault(), "%.1f", value);
+ }
+
+ @NonNull
+ public static String formatCacheInfoHistory(final Geocache cache) {
+ final List<String> infos = new ArrayList<>(3);
+ infos.add(StringUtils.upperCase(cache.getGeocode()));
+ infos.add(formatDate(cache.getVisitedDate()));
+ infos.add(formatTime(cache.getVisitedDate()));
+ return StringUtils.join(infos, SEPARATOR);
+ }
+
+ @NonNull
+ public static String formatWaypointInfo(final Waypoint waypoint) {
+ final List<String> infos = new ArrayList<>(3);
+ final WaypointType waypointType = waypoint.getWaypointType();
+ if (waypointType != WaypointType.OWN && waypointType != null) {
+ infos.add(waypointType.getL10n());
+ }
+ if (Waypoint.PREFIX_OWN.equalsIgnoreCase(waypoint.getPrefix())) {
+ infos.add(CgeoApplication.getInstance().getString(R.string.waypoint_custom));
+ } else {
+ if (StringUtils.isNotBlank(waypoint.getPrefix())) {
+ infos.add(waypoint.getPrefix());
+ }
+ if (StringUtils.isNotBlank(waypoint.getLookup())) {
+ infos.add(waypoint.getLookup());
+ }
+ }
+ return StringUtils.join(infos, SEPARATOR);
+ }
+
+ @NonNull
+ public static String formatDaysAgo(final long date) {
+ final int days = CalendarUtils.daysSince(date);
+ switch (days) {
+ case 0:
+ return CgeoApplication.getInstance().getString(R.string.log_today);
+ case 1:
+ return CgeoApplication.getInstance().getString(R.string.log_yesterday);
+ default:
+ return CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.days_ago, days, days);
+ }
+ }
+
+ /**
+ * Formatting of the hidden date of a cache
+ *
+ * @return {@code null} or hidden date of the cache (or event date of the cache) in human readable format
+ */
+ @Nullable
+ public static String formatHiddenDate(final Geocache cache) {
+ final Date hiddenDate = cache.getHiddenDate();
+ if (hiddenDate == null) {
+ return null;
+ }
+ final long time = hiddenDate.getTime();
+ if (time <= 0) {
+ return null;
+ }
+ String dateString = formatFullDate(time);
+ if (cache.isEventCache()) {
+ dateString = DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY) + ", " + dateString;
+ }
+ return dateString;
+ }
+
+ @NonNull
+ public static String formatMapSubtitle(final Geocache cache) {
+ return "D " + formatDT(cache.getDifficulty()) + SEPARATOR + "T " + formatDT(cache.getTerrain()) + SEPARATOR + cache.getGeocode();
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/utils/HtmlUtils.java b/main/src/cgeo/geocaching/utils/HtmlUtils.java index 8202cd4..0748e84 100644 --- a/main/src/cgeo/geocaching/utils/HtmlUtils.java +++ b/main/src/cgeo/geocaching/utils/HtmlUtils.java @@ -1,73 +1,76 @@ -package cgeo.geocaching.utils; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; - -import android.text.Html; -import android.text.Spanned; -import android.text.style.ImageSpan; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -public final class HtmlUtils { - - private HtmlUtils() { - // utility class - } - - /** - * Extract the text from a HTML based string. This is similar to what HTML.fromHtml(...) does, but this method also - * removes the embedded images instead of replacing them by a small rectangular representation character. - * - */ - public static String extractText(final CharSequence html) { - if (StringUtils.isBlank(html)) { - return StringUtils.EMPTY; - } - String result = html.toString(); - - // recognize images in textview HTML contents - if (html instanceof Spanned) { - final Spanned text = (Spanned) html; - final Object[] styles = text.getSpans(0, text.length(), Object.class); - final List<Pair<Integer, Integer>> removals = new ArrayList<>(); - for (final Object style : styles) { - if (style instanceof ImageSpan) { - final int start = text.getSpanStart(style); - final int end = text.getSpanEnd(style); - removals.add(Pair.of(start, end)); - } - } - - // sort reversed and delete image spans - Collections.sort(removals, new Comparator<Pair<Integer, Integer>>() { - - @Override - public int compare(final Pair<Integer, Integer> lhs, final Pair<Integer, Integer> rhs) { - return rhs.getRight().compareTo(lhs.getRight()); - } - }); - result = text.toString(); - for (final Pair<Integer, Integer> removal : removals) { - result = result.substring(0, removal.getLeft()) + result.substring(removal.getRight()); - } - } - - // now that images are gone, do a normal html to text conversion - return Html.fromHtml(result).toString().trim(); - } - - public static String removeExtraParagraph(final String htmlIn) { - final String html = StringUtils.trim(htmlIn); - if (StringUtils.startsWith(html, "<p>") && StringUtils.endsWith(html, "</p>")) { - final String paragraph = StringUtils.substring(html, "<p>".length(), html.length() - "</p>".length()).trim(); - if (extractText(paragraph).equals(paragraph)) { - return paragraph; - } - } - return html; - } -} +package cgeo.geocaching.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.eclipse.jdt.annotation.NonNull;
+
+import android.text.Html;
+import android.text.Spanned;
+import android.text.style.ImageSpan;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public final class HtmlUtils {
+
+ private HtmlUtils() {
+ // utility class
+ }
+
+ /**
+ * Extract the text from a HTML based string. This is similar to what HTML.fromHtml(...) does, but this method also
+ * removes the embedded images instead of replacing them by a small rectangular representation character.
+ *
+ */
+ @NonNull
+ public static String extractText(final CharSequence html) {
+ if (StringUtils.isBlank(html)) {
+ return StringUtils.EMPTY;
+ }
+ String result = html.toString();
+
+ // recognize images in textview HTML contents
+ if (html instanceof Spanned) {
+ final Spanned text = (Spanned) html;
+ final Object[] styles = text.getSpans(0, text.length(), Object.class);
+ final List<Pair<Integer, Integer>> removals = new ArrayList<>();
+ for (final Object style : styles) {
+ if (style instanceof ImageSpan) {
+ final int start = text.getSpanStart(style);
+ final int end = text.getSpanEnd(style);
+ removals.add(Pair.of(start, end));
+ }
+ }
+
+ // sort reversed and delete image spans
+ Collections.sort(removals, new Comparator<Pair<Integer, Integer>>() {
+
+ @Override
+ public int compare(final Pair<Integer, Integer> lhs, final Pair<Integer, Integer> rhs) {
+ return rhs.getRight().compareTo(lhs.getRight());
+ }
+ });
+ result = text.toString();
+ for (final Pair<Integer, Integer> removal : removals) {
+ result = result.substring(0, removal.getLeft()) + result.substring(removal.getRight());
+ }
+ }
+
+ // now that images are gone, do a normal html to text conversion
+ return Html.fromHtml(result).toString().trim();
+ }
+
+ @NonNull
+ public static String removeExtraParagraph(final String htmlIn) {
+ final String html = StringUtils.trim(htmlIn);
+ if (StringUtils.startsWith(html, "<p>") && StringUtils.endsWith(html, "</p>")) {
+ final String paragraph = StringUtils.substring(html, "<p>".length(), html.length() - "</p>".length()).trim();
+ if (extractText(paragraph).equals(paragraph)) {
+ return paragraph;
+ }
+ }
+ return html;
+ }
+}
diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java index 269791a..000966a 100644 --- a/main/src/cgeo/geocaching/utils/ImageUtils.java +++ b/main/src/cgeo/geocaching/utils/ImageUtils.java @@ -1,486 +1,490 @@ -package cgeo.geocaching.utils; - -import cgeo.geocaching.CgeoApplication; -import cgeo.geocaching.Image; -import cgeo.geocaching.R; -import cgeo.geocaching.compatibility.Compatibility; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - -import rx.Observable; -import rx.Scheduler.Worker; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action0; -import rx.functions.Action1; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.media.ExifInterface; -import android.net.Uri; -import android.os.Environment; -import android.text.Html; -import android.text.Html.ImageGetter; -import android.util.Base64; -import android.util.Base64InputStream; -import android.widget.TextView; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.ref.WeakReference; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; - -public final class ImageUtils { - private static final int[] ORIENTATIONS = { - ExifInterface.ORIENTATION_ROTATE_90, - ExifInterface.ORIENTATION_ROTATE_180, - ExifInterface.ORIENTATION_ROTATE_270 - }; - - private static final int[] ROTATION = { 90, 180, 270 }; - private static final int MAX_DISPLAY_IMAGE_XY = 800; - - // Images whose URL contains one of those patterns will not be available on the Images tab - // for opening into an external application. - private final static String[] NO_EXTERNAL = { "geocheck.org" }; - - private ImageUtils() { - // Do not let this class be instantiated, this is a utility class. - } - - /** - * Scales a bitmap to the device display size. - * - * @param image - * The image Bitmap representation to scale - * @return BitmapDrawable The scaled image - */ - public static BitmapDrawable scaleBitmapToFitDisplay(@NonNull final Bitmap image) { - final Point displaySize = Compatibility.getDisplaySize(); - final int maxWidth = displaySize.x - 25; - final int maxHeight = displaySize.y - 25; - return scaleBitmapTo(image, maxWidth, maxHeight); - } - - /** - * Reads and scales an image file to the device display size. - * - * @param filename - * The image file to read and scale - * @return Bitmap The scaled image or Null if source image can't be read - */ - @Nullable - public static Bitmap readAndScaleImageToFitDisplay(@NonNull final String filename) { - final Point displaySize = Compatibility.getDisplaySize(); - // Restrict image size to 800 x 800 to prevent OOM on tablets - final int maxWidth = Math.min(displaySize.x - 25, MAX_DISPLAY_IMAGE_XY); - final int maxHeight = Math.min(displaySize.y - 25, MAX_DISPLAY_IMAGE_XY); - final Bitmap image = readDownsampledImage(filename, maxWidth, maxHeight); - if (image == null) { - return null; - } - final BitmapDrawable scaledImage = scaleBitmapTo(image, maxWidth, maxHeight); - return scaledImage.getBitmap(); - } - - /** - * Scales a bitmap to the given bounds if it is larger, otherwise returns the original bitmap. - * - * @param image - * The bitmap to scale - * @return BitmapDrawable The scaled image - */ - @NonNull - private static BitmapDrawable scaleBitmapTo(@NonNull final Bitmap image, final int maxWidth, final int maxHeight) { - final CgeoApplication app = CgeoApplication.getInstance(); - Bitmap result = image; - int width = image.getWidth(); - int height = image.getHeight(); - - if (width > maxWidth || height > maxHeight) { - final double ratio = Math.min((double) maxHeight / (double) height, (double) maxWidth / (double) width); - width = (int) Math.ceil(width * ratio); - height = (int) Math.ceil(height * ratio); - result = Bitmap.createScaledBitmap(image, width, height, true); - } - - final BitmapDrawable resultDrawable = new BitmapDrawable(app.getResources(), result); - resultDrawable.setBounds(new Rect(0, 0, width, height)); - return resultDrawable; - } - - /** - * Store a bitmap to file. - * - * @param bitmap - * The bitmap to store - * @param format - * The image format - * @param quality - * The image quality - * @param pathOfOutputImage - * Path to store to - */ - public static void storeBitmap(final Bitmap bitmap, final Bitmap.CompressFormat format, final int quality, final String pathOfOutputImage) { - try { - final FileOutputStream out = new FileOutputStream(pathOfOutputImage); - final BufferedOutputStream bos = new BufferedOutputStream(out); - bitmap.compress(format, quality, bos); - bos.flush(); - bos.close(); - } catch (final IOException e) { - Log.e("ImageHelper.storeBitmap", e); - } - } - - /** - * Scales an image to the desired bounds and encodes to file. - * - * @param filePath - * Image to read - * @param maxXY - * bounds - * @return filename and path, <tt>null</tt> if something fails - */ - @Nullable - public static String readScaleAndWriteImage(@NonNull final String filePath, final int maxXY) { - if (maxXY <= 0) { - return filePath; - } - final Bitmap image = readDownsampledImage(filePath, maxXY, maxXY); - if (image == null) { - return null; - } - final BitmapDrawable scaledImage = scaleBitmapTo(image, maxXY, maxXY); - final File tempImageFile = getOutputImageFile(); - if (tempImageFile == null) { - Log.e("ImageUtils.readScaleAndWriteImage: unable to write scaled image"); - return null; - } - final String uploadFilename = tempImageFile.getPath(); - storeBitmap(scaledImage.getBitmap(), Bitmap.CompressFormat.JPEG, 75, uploadFilename); - return uploadFilename; - } - - /** - * Reads and scales an image file with downsampling in one step to prevent memory consumption. - * - * @param filePath - * The file to read - * @param maxX - * The desired width - * @param maxY - * The desired height - * @return Bitmap the image or null if file can't be read - */ - @Nullable - public static Bitmap readDownsampledImage(@NonNull final String filePath, final int maxX, final int maxY) { - int orientation = ExifInterface.ORIENTATION_NORMAL; - try { - final ExifInterface exif = new ExifInterface(filePath); - orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - } catch (final IOException e) { - Log.e("ImageUtils.readDownsampledImage", e); - } - final BitmapFactory.Options sizeOnlyOptions = new BitmapFactory.Options(); - sizeOnlyOptions.inJustDecodeBounds = true; - BitmapFactory.decodeFile(filePath, sizeOnlyOptions); - final int myMaxXY = Math.max(sizeOnlyOptions.outHeight, sizeOnlyOptions.outWidth); - final int maxXY = Math.max(maxX, maxY); - final int sampleSize = myMaxXY / maxXY; - final BitmapFactory.Options sampleOptions = new BitmapFactory.Options(); - if (sampleSize > 1) { - sampleOptions.inSampleSize = sampleSize; - } - final Bitmap decodedImage = BitmapFactory.decodeFile(filePath, sampleOptions); - if (decodedImage != null) { - for (int i = 0; i < ORIENTATIONS.length; i++) { - if (orientation == ORIENTATIONS[i]) { - final Matrix matrix = new Matrix(); - matrix.postRotate(ROTATION[i]); - return Bitmap.createBitmap(decodedImage, 0, 0, decodedImage.getWidth(), decodedImage.getHeight(), matrix, true); - } - } - } - return decodedImage; - } - - /** Create a File for saving an image or video - * - * @return the temporary image file to use, or <tt>null</tt> if the media directory could - * not be created. - * */ - @Nullable - public static File getOutputImageFile() { - // To be safe, you should check that the SDCard is mounted - // using Environment.getExternalStorageState() before doing this. - final File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "cgeo"); - - // This location works best if you want the created images to be shared - // between applications and persist after your app has been uninstalled. - - // Create the storage directory if it does not exist - if (!mediaStorageDir.exists()) { - if (!FileUtils.mkdirs(mediaStorageDir)) { - Log.e("ImageUtils.getOutputImageFile: cannot create media storage directory"); - return null; - } - } - - // Create a media file name - final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); - return new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"); - } - - @Nullable - public static Uri getOutputImageFileUri() { - final File file = getOutputImageFile(); - if (file == null) { - return null; - } - return Uri.fromFile(file); - } - - /** - * Check if the URL contains one of the given substrings. - * - * @param url the URL to check - * @param patterns a list of substrings to check against - * @return <tt>true</tt> if the URL contains at least one of the patterns, <tt>false</tt> otherwise - */ - public static boolean containsPattern(final String url, final String[] patterns) { - for (final String entry : patterns) { - if (StringUtils.containsIgnoreCase(url, entry)) { - return true; - } - } - return false; - } - - /** - * Decode a base64-encoded string and save the result into a file. - * - * @param inString the encoded string - * @param outFile the file to save the decoded result into - */ - public static void decodeBase64ToFile(final String inString, final File outFile) { - FileOutputStream out = null; - try { - out = new FileOutputStream(outFile); - decodeBase64ToStream(inString, out); - } catch (final IOException e) { - Log.e("HtmlImage.decodeBase64ToFile: cannot write file for decoded inline image", e); - } finally { - IOUtils.closeQuietly(out); - } - } - - /** - * Decode a base64-encoded string and save the result into a stream. - * - * @param inString - * the encoded string - * @param out - * the stream to save the decoded result into - */ - public static void decodeBase64ToStream(final String inString, final OutputStream out) throws IOException { - Base64InputStream in = null; - try { - in = new Base64InputStream(new ByteArrayInputStream(inString.getBytes(TextUtils.CHARSET_ASCII)), Base64.DEFAULT); - IOUtils.copy(in, out); - } finally { - IOUtils.closeQuietly(in); - } - } - - public static BitmapDrawable getTransparent1x1Drawable(final Resources res) { - return new BitmapDrawable(res, BitmapFactory.decodeResource(res, R.drawable.image_no_placement)); - } - - /** - * Add images present in the HTML description to the existing collection. - * @param images a collection of images - * @param geocode the common title for images in the description - * @param htmlText the HTML description to be parsed, can be repeated - */ - public static void addImagesFromHtml(final Collection<Image> images, final String geocode, final String... htmlText) { - final Set<String> urls = new LinkedHashSet<>(); - for (final Image image : images) { - urls.add(image.getUrl()); - } - for (final String text: htmlText) { - Html.fromHtml(StringUtils.defaultString(text), new ImageGetter() { - @Override - public Drawable getDrawable(final String source) { - if (!urls.contains(source) && canBeOpenedExternally(source)) { - images.add(new Image.Builder() - .setUrl(source) - .setTitle(StringUtils.defaultString(geocode)) - .build()); - urls.add(source); - } - return null; - } - }, null); - } - } - - /** - * Container which can hold a drawable (initially an empty one) and get a newer version when it - * becomes available. It also invalidates the view the container belongs to, so that it is - * redrawn properly. - * <p/> - * When a new version of the drawable is available, it is put into a queue and, if needed (no other elements - * waiting in the queue), a refresh is launched on the UI thread. This refresh will empty the queue (including - * elements arrived in the meantime) and ensures that the view is uploaded only once all the queued requests have - * been handled. - */ - public static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> { - final private static Object lock = new Object(); // Used to lock the queue to determine if a refresh needs to be scheduled - final private static LinkedBlockingQueue<ImmutablePair<ContainerDrawable, Drawable>> REDRAW_QUEUE = new LinkedBlockingQueue<>(); - final private static Set<TextView> VIEWS = new HashSet<>(); // Modified only on the UI thread - final private static Worker UI_WORKER = AndroidSchedulers.mainThread().createWorker(); - final private static Action0 REDRAW_QUEUED_DRAWABLES = new Action0() { - @Override - public void call() { - redrawQueuedDrawables(); - } - }; - - private Drawable drawable; - final protected WeakReference<TextView> viewRef; - - @SuppressWarnings("deprecation") - public ContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) { - viewRef = new WeakReference<>(view); - drawable = null; - setBounds(0, 0, 0, 0); - drawableObservable.subscribe(this); - } - - @Override - public final void draw(final Canvas canvas) { - if (drawable != null) { - drawable.draw(canvas); - } - } - - @Override - public final void call(final Drawable newDrawable) { - final boolean needsRedraw; - synchronized (lock) { - // Check for emptyness inside the call to match the behaviour in redrawQueuedDrawables(). - needsRedraw = REDRAW_QUEUE.isEmpty(); - REDRAW_QUEUE.add(ImmutablePair.of(this, newDrawable)); - } - if (needsRedraw) { - UI_WORKER.schedule(REDRAW_QUEUED_DRAWABLES); - } - } - - /** - * Update the container with the new drawable. Called on the UI thread. - * - * @param newDrawable the new drawable - * @return the view to update or <tt>null</tt> if the view is not alive anymore - */ - protected TextView updateDrawable(final Drawable newDrawable) { - setBounds(0, 0, newDrawable.getIntrinsicWidth(), newDrawable.getIntrinsicHeight()); - drawable = newDrawable; - return viewRef.get(); - } - - private static void redrawQueuedDrawables() { - if (!REDRAW_QUEUE.isEmpty()) { - // Add a small margin so that drawables arriving between the beginning of the allocation and the draining - // of the queue might be absorbed without reallocation. - final List<ImmutablePair<ContainerDrawable, Drawable>> toRedraw = new ArrayList<>(REDRAW_QUEUE.size() + 16); - synchronized (lock) { - // Empty the queue inside the lock to match the check done in call(). - REDRAW_QUEUE.drainTo(toRedraw); - } - for (final ImmutablePair<ContainerDrawable, Drawable> redrawable : toRedraw) { - final TextView view = redrawable.left.updateDrawable(redrawable.right); - if (view != null) { - VIEWS.add(view); - } - } - for (final TextView view : VIEWS) { - view.setText(view.getText()); - } - VIEWS.clear(); - } - } - - } - - /** - * Image that automatically scales to fit a line of text in the containing {@link TextView}. - */ - public final static class LineHeightContainerDrawable extends ContainerDrawable { - public LineHeightContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) { - super(view, drawableObservable); - } - - @Override - protected TextView updateDrawable(final Drawable newDrawable) { - final TextView view = super.updateDrawable(newDrawable); - if (view != null) { - setBounds(scaleImageToLineHeight(newDrawable, view)); - } - return view; - } - } - - public static boolean canBeOpenedExternally(final String source) { - return !containsPattern(source, NO_EXTERNAL); - } - - public static Rect scaleImageToLineHeight(final Drawable drawable, final TextView view) { - final int lineHeight = (int) (view.getLineHeight() * 0.8); - final int width = drawable.getIntrinsicWidth() * lineHeight / drawable.getIntrinsicHeight(); - return new Rect(0, 0, width, lineHeight); - } - - public static Bitmap convertToBitmap(final Drawable drawable) { - if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable) drawable).getBitmap(); - } - - // handle solid colors, which have no width - int width = drawable.getIntrinsicWidth(); - width = width > 0 ? width : 1; - int height = drawable.getIntrinsicHeight(); - height = height > 0 ? height : 1; - - final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - - return bitmap; - } -} +package cgeo.geocaching.utils;
+
+import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.Image;
+import cgeo.geocaching.R;
+import cgeo.geocaching.compatibility.Compatibility;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.Environment;
+import android.text.Html;
+import android.text.Html.ImageGetter;
+import android.util.Base64;
+import android.util.Base64InputStream;
+import android.widget.TextView;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import rx.Observable;
+import rx.Scheduler.Worker;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.functions.Action0;
+import rx.functions.Action1;
+
+public final class ImageUtils {
+ private static final int[] ORIENTATIONS = {
+ ExifInterface.ORIENTATION_ROTATE_90,
+ ExifInterface.ORIENTATION_ROTATE_180,
+ ExifInterface.ORIENTATION_ROTATE_270
+ };
+
+ private static final int[] ROTATION = { 90, 180, 270 };
+ private static final int MAX_DISPLAY_IMAGE_XY = 800;
+
+ // Images whose URL contains one of those patterns will not be available on the Images tab
+ // for opening into an external application.
+ private final static String[] NO_EXTERNAL = { "geocheck.org" };
+
+ private ImageUtils() {
+ // Do not let this class be instantiated, this is a utility class.
+ }
+
+ /**
+ * Scales a bitmap to the device display size.
+ *
+ * @param image
+ * The image Bitmap representation to scale
+ * @return BitmapDrawable The scaled image
+ */
+ @NonNull
+ public static BitmapDrawable scaleBitmapToFitDisplay(@NonNull final Bitmap image) {
+ final Point displaySize = Compatibility.getDisplaySize();
+ final int maxWidth = displaySize.x - 25;
+ final int maxHeight = displaySize.y - 25;
+ return scaleBitmapTo(image, maxWidth, maxHeight);
+ }
+
+ /**
+ * Reads and scales an image file to the device display size.
+ *
+ * @param filename
+ * The image file to read and scale
+ * @return Bitmap The scaled image or Null if source image can't be read
+ */
+ @Nullable
+ public static Bitmap readAndScaleImageToFitDisplay(@NonNull final String filename) {
+ final Point displaySize = Compatibility.getDisplaySize();
+ // Restrict image size to 800 x 800 to prevent OOM on tablets
+ final int maxWidth = Math.min(displaySize.x - 25, MAX_DISPLAY_IMAGE_XY);
+ final int maxHeight = Math.min(displaySize.y - 25, MAX_DISPLAY_IMAGE_XY);
+ final Bitmap image = readDownsampledImage(filename, maxWidth, maxHeight);
+ if (image == null) {
+ return null;
+ }
+ final BitmapDrawable scaledImage = scaleBitmapTo(image, maxWidth, maxHeight);
+ return scaledImage.getBitmap();
+ }
+
+ /**
+ * Scales a bitmap to the given bounds if it is larger, otherwise returns the original bitmap.
+ *
+ * @param image
+ * The bitmap to scale
+ * @return BitmapDrawable The scaled image
+ */
+ @NonNull
+ private static BitmapDrawable scaleBitmapTo(@NonNull final Bitmap image, final int maxWidth, final int maxHeight) {
+ final CgeoApplication app = CgeoApplication.getInstance();
+ Bitmap result = image;
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ if (width > maxWidth || height > maxHeight) {
+ final double ratio = Math.min((double) maxHeight / (double) height, (double) maxWidth / (double) width);
+ width = (int) Math.ceil(width * ratio);
+ height = (int) Math.ceil(height * ratio);
+ result = Bitmap.createScaledBitmap(image, width, height, true);
+ }
+
+ final BitmapDrawable resultDrawable = new BitmapDrawable(app.getResources(), result);
+ resultDrawable.setBounds(new Rect(0, 0, width, height));
+ return resultDrawable;
+ }
+
+ /**
+ * Store a bitmap to file.
+ *
+ * @param bitmap
+ * The bitmap to store
+ * @param format
+ * The image format
+ * @param quality
+ * The image quality
+ * @param pathOfOutputImage
+ * Path to store to
+ */
+ public static void storeBitmap(final Bitmap bitmap, final Bitmap.CompressFormat format, final int quality, final String pathOfOutputImage) {
+ try {
+ final FileOutputStream out = new FileOutputStream(pathOfOutputImage);
+ final BufferedOutputStream bos = new BufferedOutputStream(out);
+ bitmap.compress(format, quality, bos);
+ bos.flush();
+ bos.close();
+ } catch (final IOException e) {
+ Log.e("ImageHelper.storeBitmap", e);
+ }
+ }
+
+ /**
+ * Scales an image to the desired bounds and encodes to file.
+ *
+ * @param filePath
+ * Image to read
+ * @param maxXY
+ * bounds
+ * @return filename and path, <tt>null</tt> if something fails
+ */
+ @Nullable
+ public static String readScaleAndWriteImage(@NonNull final String filePath, final int maxXY) {
+ if (maxXY <= 0) {
+ return filePath;
+ }
+ final Bitmap image = readDownsampledImage(filePath, maxXY, maxXY);
+ if (image == null) {
+ return null;
+ }
+ final BitmapDrawable scaledImage = scaleBitmapTo(image, maxXY, maxXY);
+ final File tempImageFile = getOutputImageFile();
+ if (tempImageFile == null) {
+ Log.e("ImageUtils.readScaleAndWriteImage: unable to write scaled image");
+ return null;
+ }
+ final String uploadFilename = tempImageFile.getPath();
+ storeBitmap(scaledImage.getBitmap(), Bitmap.CompressFormat.JPEG, 75, uploadFilename);
+ return uploadFilename;
+ }
+
+ /**
+ * Reads and scales an image file with downsampling in one step to prevent memory consumption.
+ *
+ * @param filePath
+ * The file to read
+ * @param maxX
+ * The desired width
+ * @param maxY
+ * The desired height
+ * @return Bitmap the image or null if file can't be read
+ */
+ @Nullable
+ public static Bitmap readDownsampledImage(@NonNull final String filePath, final int maxX, final int maxY) {
+ int orientation = ExifInterface.ORIENTATION_NORMAL;
+ try {
+ final ExifInterface exif = new ExifInterface(filePath);
+ orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
+ } catch (final IOException e) {
+ Log.e("ImageUtils.readDownsampledImage", e);
+ }
+ final BitmapFactory.Options sizeOnlyOptions = new BitmapFactory.Options();
+ sizeOnlyOptions.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(filePath, sizeOnlyOptions);
+ final int myMaxXY = Math.max(sizeOnlyOptions.outHeight, sizeOnlyOptions.outWidth);
+ final int maxXY = Math.max(maxX, maxY);
+ final int sampleSize = myMaxXY / maxXY;
+ final BitmapFactory.Options sampleOptions = new BitmapFactory.Options();
+ if (sampleSize > 1) {
+ sampleOptions.inSampleSize = sampleSize;
+ }
+ final Bitmap decodedImage = BitmapFactory.decodeFile(filePath, sampleOptions);
+ if (decodedImage != null) {
+ for (int i = 0; i < ORIENTATIONS.length; i++) {
+ if (orientation == ORIENTATIONS[i]) {
+ final Matrix matrix = new Matrix();
+ matrix.postRotate(ROTATION[i]);
+ return Bitmap.createBitmap(decodedImage, 0, 0, decodedImage.getWidth(), decodedImage.getHeight(), matrix, true);
+ }
+ }
+ }
+ return decodedImage;
+ }
+
+ /** Create a File for saving an image or video
+ *
+ * @return the temporary image file to use, or <tt>null</tt> if the media directory could
+ * not be created.
+ * */
+ @Nullable
+ public static File getOutputImageFile() {
+ // To be safe, you should check that the SDCard is mounted
+ // using Environment.getExternalStorageState() before doing this.
+ final File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "cgeo");
+
+ // This location works best if you want the created images to be shared
+ // between applications and persist after your app has been uninstalled.
+
+ // Create the storage directory if it does not exist
+ if (!mediaStorageDir.exists()) {
+ if (!FileUtils.mkdirs(mediaStorageDir)) {
+ Log.e("ImageUtils.getOutputImageFile: cannot create media storage directory");
+ return null;
+ }
+ }
+
+ // Create a media file name
+ final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
+ return new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
+ }
+
+ @Nullable
+ public static Uri getOutputImageFileUri() {
+ final File file = getOutputImageFile();
+ if (file == null) {
+ return null;
+ }
+ return Uri.fromFile(file);
+ }
+
+ /**
+ * Check if the URL contains one of the given substrings.
+ *
+ * @param url the URL to check
+ * @param patterns a list of substrings to check against
+ * @return <tt>true</tt> if the URL contains at least one of the patterns, <tt>false</tt> otherwise
+ */
+ public static boolean containsPattern(final String url, final String[] patterns) {
+ for (final String entry : patterns) {
+ if (StringUtils.containsIgnoreCase(url, entry)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Decode a base64-encoded string and save the result into a file.
+ *
+ * @param inString the encoded string
+ * @param outFile the file to save the decoded result into
+ */
+ public static void decodeBase64ToFile(final String inString, final File outFile) {
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(outFile);
+ decodeBase64ToStream(inString, out);
+ } catch (final IOException e) {
+ Log.e("HtmlImage.decodeBase64ToFile: cannot write file for decoded inline image", e);
+ } finally {
+ IOUtils.closeQuietly(out);
+ }
+ }
+
+ /**
+ * Decode a base64-encoded string and save the result into a stream.
+ *
+ * @param inString
+ * the encoded string
+ * @param out
+ * the stream to save the decoded result into
+ */
+ public static void decodeBase64ToStream(final String inString, final OutputStream out) throws IOException {
+ Base64InputStream in = null;
+ try {
+ in = new Base64InputStream(new ByteArrayInputStream(inString.getBytes(TextUtils.CHARSET_ASCII)), Base64.DEFAULT);
+ IOUtils.copy(in, out);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ }
+
+ @NonNull
+ public static BitmapDrawable getTransparent1x1Drawable(final Resources res) {
+ return new BitmapDrawable(res, BitmapFactory.decodeResource(res, R.drawable.image_no_placement));
+ }
+
+ /**
+ * Add images present in the HTML description to the existing collection.
+ * @param images a collection of images
+ * @param geocode the common title for images in the description
+ * @param htmlText the HTML description to be parsed, can be repeated
+ */
+ public static void addImagesFromHtml(final Collection<Image> images, final String geocode, final String... htmlText) {
+ final Set<String> urls = new LinkedHashSet<>();
+ for (final Image image : images) {
+ urls.add(image.getUrl());
+ }
+ for (final String text: htmlText) {
+ Html.fromHtml(StringUtils.defaultString(text), new ImageGetter() {
+ @Override
+ public Drawable getDrawable(final String source) {
+ if (!urls.contains(source) && canBeOpenedExternally(source)) {
+ images.add(new Image.Builder()
+ .setUrl(source)
+ .setTitle(StringUtils.defaultString(geocode))
+ .build());
+ urls.add(source);
+ }
+ return null;
+ }
+ }, null);
+ }
+ }
+
+ /**
+ * Container which can hold a drawable (initially an empty one) and get a newer version when it
+ * becomes available. It also invalidates the view the container belongs to, so that it is
+ * redrawn properly.
+ * <p/>
+ * When a new version of the drawable is available, it is put into a queue and, if needed (no other elements
+ * waiting in the queue), a refresh is launched on the UI thread. This refresh will empty the queue (including
+ * elements arrived in the meantime) and ensures that the view is uploaded only once all the queued requests have
+ * been handled.
+ */
+ public static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> {
+ final private static Object lock = new Object(); // Used to lock the queue to determine if a refresh needs to be scheduled
+ final private static LinkedBlockingQueue<ImmutablePair<ContainerDrawable, Drawable>> REDRAW_QUEUE = new LinkedBlockingQueue<>();
+ final private static Set<TextView> VIEWS = new HashSet<>(); // Modified only on the UI thread
+ final private static Worker UI_WORKER = AndroidSchedulers.mainThread().createWorker();
+ final private static Action0 REDRAW_QUEUED_DRAWABLES = new Action0() {
+ @Override
+ public void call() {
+ redrawQueuedDrawables();
+ }
+ };
+
+ private Drawable drawable;
+ final protected WeakReference<TextView> viewRef;
+
+ @SuppressWarnings("deprecation")
+ public ContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) {
+ viewRef = new WeakReference<>(view);
+ drawable = null;
+ setBounds(0, 0, 0, 0);
+ drawableObservable.subscribe(this);
+ }
+
+ @Override
+ public final void draw(final Canvas canvas) {
+ if (drawable != null) {
+ drawable.draw(canvas);
+ }
+ }
+
+ @Override
+ public final void call(final Drawable newDrawable) {
+ final boolean needsRedraw;
+ synchronized (lock) {
+ // Check for emptyness inside the call to match the behaviour in redrawQueuedDrawables().
+ needsRedraw = REDRAW_QUEUE.isEmpty();
+ REDRAW_QUEUE.add(ImmutablePair.of(this, newDrawable));
+ }
+ if (needsRedraw) {
+ UI_WORKER.schedule(REDRAW_QUEUED_DRAWABLES);
+ }
+ }
+
+ /**
+ * Update the container with the new drawable. Called on the UI thread.
+ *
+ * @param newDrawable the new drawable
+ * @return the view to update or <tt>null</tt> if the view is not alive anymore
+ */
+ protected TextView updateDrawable(final Drawable newDrawable) {
+ setBounds(0, 0, newDrawable.getIntrinsicWidth(), newDrawable.getIntrinsicHeight());
+ drawable = newDrawable;
+ return viewRef.get();
+ }
+
+ private static void redrawQueuedDrawables() {
+ if (!REDRAW_QUEUE.isEmpty()) {
+ // Add a small margin so that drawables arriving between the beginning of the allocation and the draining
+ // of the queue might be absorbed without reallocation.
+ final List<ImmutablePair<ContainerDrawable, Drawable>> toRedraw = new ArrayList<>(REDRAW_QUEUE.size() + 16);
+ synchronized (lock) {
+ // Empty the queue inside the lock to match the check done in call().
+ REDRAW_QUEUE.drainTo(toRedraw);
+ }
+ for (final ImmutablePair<ContainerDrawable, Drawable> redrawable : toRedraw) {
+ final TextView view = redrawable.left.updateDrawable(redrawable.right);
+ if (view != null) {
+ VIEWS.add(view);
+ }
+ }
+ for (final TextView view : VIEWS) {
+ view.setText(view.getText());
+ }
+ VIEWS.clear();
+ }
+ }
+
+ }
+
+ /**
+ * Image that automatically scales to fit a line of text in the containing {@link TextView}.
+ */
+ public final static class LineHeightContainerDrawable extends ContainerDrawable {
+ public LineHeightContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) {
+ super(view, drawableObservable);
+ }
+
+ @Override
+ protected TextView updateDrawable(final Drawable newDrawable) {
+ final TextView view = super.updateDrawable(newDrawable);
+ if (view != null) {
+ setBounds(scaleImageToLineHeight(newDrawable, view));
+ }
+ return view;
+ }
+ }
+
+ public static boolean canBeOpenedExternally(final String source) {
+ return !containsPattern(source, NO_EXTERNAL);
+ }
+
+ @NonNull
+ public static Rect scaleImageToLineHeight(final Drawable drawable, final TextView view) {
+ final int lineHeight = (int) (view.getLineHeight() * 0.8);
+ final int width = drawable.getIntrinsicWidth() * lineHeight / drawable.getIntrinsicHeight();
+ return new Rect(0, 0, width, lineHeight);
+ }
+
+ @Nullable
+ public static Bitmap convertToBitmap(final Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+
+ // handle solid colors, which have no width
+ int width = drawable.getIntrinsicWidth();
+ width = width > 0 ? width : 1;
+ int height = drawable.getIntrinsicHeight();
+ height = height > 0 ? height : 1;
+
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+
+ return bitmap;
+ }
+}
diff --git a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java index 97e2187..b54f5d4 100644 --- a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java +++ b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java @@ -1,274 +1,277 @@ -package cgeo.geocaching.utils; - -import cgeo.geocaching.Geocache; -import cgeo.geocaching.LogEntry; -import cgeo.geocaching.R; -import cgeo.geocaching.Trackable; -import cgeo.geocaching.connector.ConnectorFactory; -import cgeo.geocaching.connector.IConnector; -import cgeo.geocaching.connector.capability.ILogin; -import cgeo.geocaching.settings.Settings; - -import org.apache.commons.lang3.StringUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * Provides all the available templates for logging. - * - */ -public final class LogTemplateProvider { - - private LogTemplateProvider() { - // utility class - } - - /** - * Context aware data container for log templates. - * <p> - * Some log templates need additional information. To provide that information, it can be encapsulated in this log - * context. - * </p> - * - */ - public static class LogContext { - private Geocache cache; - private Trackable trackable; - private boolean offline = false; - private final LogEntry logEntry; - - public LogContext(final Geocache cache, final LogEntry logEntry) { - this(cache, logEntry, false); - } - - public LogContext(final Trackable trackable, final LogEntry logEntry) { - this.trackable = trackable; - this.logEntry = logEntry; - } - - public LogContext(final Geocache cache, final LogEntry logEntry, final boolean offline) { - this.cache = cache; - this.offline = offline; - this.logEntry = logEntry; - } - - public final Geocache getCache() { - return cache; - } - - public final Trackable getTrackable() { - return trackable; - } - - public final boolean isOffline() { - return offline; - } - - public final LogEntry getLogEntry() { - return logEntry; - } - } - - public abstract static class LogTemplate { - private final String template; - private final int resourceId; - - protected LogTemplate(final String template, final int resourceId) { - this.template = template; - this.resourceId = resourceId; - } - - public abstract String getValue(LogContext context); - - public final int getResourceId() { - return resourceId; - } - - public final int getItemId() { - return template.hashCode(); - } - - public final String getTemplateString() { - return template; - } - - private final String apply(final String input, final LogContext context) { - final String bracketedTemplate = "[" + template + "]"; - - // check containment first to not unconditionally call the getValue(...) method - if (input.contains(bracketedTemplate)) { - return StringUtils.replace(input, bracketedTemplate, getValue(context)); - } - return input; - } - } - - /** - * @return all templates, but not the signature template itself - */ - public static List<LogTemplate> getTemplatesWithoutSignature() { - final List<LogTemplate> templates = new ArrayList<>(); - templates.add(new LogTemplate("DATE", R.string.init_signature_template_date) { - - @Override - public String getValue(final LogContext context) { - return Formatter.formatFullDate(System.currentTimeMillis()); - } - }); - templates.add(new LogTemplate("TIME", R.string.init_signature_template_time) { - - @Override - public String getValue(final LogContext context) { - return Formatter.formatTime(System.currentTimeMillis()); - } - }); - templates.add(new LogTemplate("DATETIME", R.string.init_signature_template_datetime) { - - @Override - public String getValue(final LogContext context) { - final long currentTime = System.currentTimeMillis(); - return Formatter.formatFullDate(currentTime) + " " + Formatter.formatTime(currentTime); - } - }); - templates.add(new LogTemplate("USER", R.string.init_signature_template_user) { - - @Override - public String getValue(final LogContext context) { - final Geocache cache = context.getCache(); - if (cache != null) { - final IConnector connector = ConnectorFactory.getConnector(cache); - if (connector instanceof ILogin) { - return ((ILogin) connector).getUserName(); - } - } - return Settings.getUsername(); - } - }); - templates.add(new LogTemplate("NUMBER", R.string.init_signature_template_number) { - - @Override - public String getValue(final LogContext context) { - final Geocache cache = context.getCache(); - if (cache == null) { - return StringUtils.EMPTY; - } - - int current = 0; - final IConnector connector = ConnectorFactory.getConnector(cache); - if (connector instanceof ILogin) { - current = ((ILogin) connector).getCachesFound(); - } - - // try updating the login information, if the counter is zero - if (current == 0) { - if (context.isOffline()) { - return StringUtils.EMPTY; - } - if (connector instanceof ILogin) { - ((ILogin) connector).login(null, null); - current = ((ILogin) connector).getCachesFound(); - } - } - - if (current >= 0) { - return String.valueOf(current + 1); - } - return StringUtils.EMPTY; - } - }); - templates.add(new LogTemplate("OWNER", R.string.init_signature_template_owner) { - - @Override - public String getValue(final LogContext context) { - final Trackable trackable = context.getTrackable(); - if (trackable != null) { - return trackable.getOwner(); - } - final Geocache cache = context.getCache(); - if (cache != null) { - return cache.getOwnerDisplayName(); - } - return StringUtils.EMPTY; - } - }); - templates.add(new LogTemplate("NAME", R.string.init_signature_template_name) { - @Override - public String getValue(final LogContext context) { - final Trackable trackable = context.getTrackable(); - if (trackable != null) { - return trackable.getName(); - } - final Geocache cache = context.getCache(); - if (cache != null) { - return cache.getName(); - } - return StringUtils.EMPTY; - } - }); - templates.add(new LogTemplate("URL", R.string.init_signature_template_url) { - - @Override - public String getValue(final LogContext context) { - final Trackable trackable = context.getTrackable(); - if (trackable != null) { - return trackable.getUrl(); - } - final Geocache cache = context.getCache(); - if (cache != null) { - return StringUtils.defaultString(cache.getUrl()); - } - return StringUtils.EMPTY; - } - }); - templates.add(new LogTemplate("LOG", R.string.init_signature_template_log) { - @Override - public String getValue(final LogContext context) { - final LogEntry logEntry = context.getLogEntry(); - if (logEntry != null) { - return logEntry.getDisplayText(); - } - return StringUtils.EMPTY; - } - }); - return templates; - } - - /** - * @return all templates, including the signature template - */ - public static List<LogTemplate> getTemplatesWithSignature() { - final List<LogTemplate> templates = getTemplatesWithoutSignature(); - templates.add(new LogTemplate("SIGNATURE", R.string.init_signature) { - @Override - public String getValue(final LogContext context) { - final String nestedTemplate = StringUtils.defaultString(Settings.getSignature()); - if (StringUtils.contains(nestedTemplate, "SIGNATURE")) { - return "invalid signature template"; - } - return applyTemplates(nestedTemplate, context); - } - }); - return templates; - } - - public static LogTemplate getTemplate(final int itemId) { - for (final LogTemplate template : getTemplatesWithSignature()) { - if (template.getItemId() == itemId) { - return template; - } - } - return null; - } - - public static String applyTemplates(final String signature, final LogContext context) { - if (signature == null) { - return StringUtils.EMPTY; - } - String result = signature; - for (final LogTemplate template : getTemplatesWithSignature()) { - result = template.apply(result, context); - } - return result; - } -} +package cgeo.geocaching.utils;
+
+import cgeo.geocaching.Geocache;
+import cgeo.geocaching.LogEntry;
+import cgeo.geocaching.R;
+import cgeo.geocaching.Trackable;
+import cgeo.geocaching.connector.ConnectorFactory;
+import cgeo.geocaching.connector.IConnector;
+import cgeo.geocaching.connector.capability.ILogin;
+import cgeo.geocaching.settings.Settings;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides all the available templates for logging.
+ *
+ */
+public final class LogTemplateProvider {
+
+ private LogTemplateProvider() {
+ // utility class
+ }
+
+ /**
+ * Context aware data container for log templates.
+ * <p>
+ * Some log templates need additional information. To provide that information, it can be encapsulated in this log
+ * context.
+ * </p>
+ *
+ */
+ public static class LogContext {
+ private Geocache cache;
+ private Trackable trackable;
+ private boolean offline = false;
+ private final LogEntry logEntry;
+
+ public LogContext(final Geocache cache, final LogEntry logEntry) {
+ this(cache, logEntry, false);
+ }
+
+ public LogContext(final Trackable trackable, final LogEntry logEntry) {
+ this.trackable = trackable;
+ this.logEntry = logEntry;
+ }
+
+ public LogContext(final Geocache cache, final LogEntry logEntry, final boolean offline) {
+ this.cache = cache;
+ this.offline = offline;
+ this.logEntry = logEntry;
+ }
+
+ public final Geocache getCache() {
+ return cache;
+ }
+
+ public final Trackable getTrackable() {
+ return trackable;
+ }
+
+ public final boolean isOffline() {
+ return offline;
+ }
+
+ public final LogEntry getLogEntry() {
+ return logEntry;
+ }
+ }
+
+ public abstract static class LogTemplate {
+ private final String template;
+ private final int resourceId;
+
+ protected LogTemplate(final String template, final int resourceId) {
+ this.template = template;
+ this.resourceId = resourceId;
+ }
+
+ public abstract String getValue(LogContext context);
+
+ public final int getResourceId() {
+ return resourceId;
+ }
+
+ public final int getItemId() {
+ return template.hashCode();
+ }
+
+ public final String getTemplateString() {
+ return template;
+ }
+
+ @NonNull
+ private final String apply(@NonNull final String input, final LogContext context) {
+ final String bracketedTemplate = "[" + template + "]";
+
+ // check containment first to not unconditionally call the getValue(...) method
+ if (input.contains(bracketedTemplate)) {
+ return StringUtils.replace(input, bracketedTemplate, getValue(context));
+ }
+ return input;
+ }
+ }
+
+ /**
+ * @return all templates, but not the signature template itself
+ */
+ @NonNull
+ public static List<LogTemplate> getTemplatesWithoutSignature() {
+ final List<LogTemplate> templates = new ArrayList<>();
+ templates.add(new LogTemplate("DATE", R.string.init_signature_template_date) {
+
+ @Override
+ public String getValue(final LogContext context) {
+ return Formatter.formatFullDate(System.currentTimeMillis());
+ }
+ });
+ templates.add(new LogTemplate("TIME", R.string.init_signature_template_time) {
+
+ @Override
+ public String getValue(final LogContext context) {
+ return Formatter.formatTime(System.currentTimeMillis());
+ }
+ });
+ templates.add(new LogTemplate("DATETIME", R.string.init_signature_template_datetime) {
+
+ @Override
+ public String getValue(final LogContext context) {
+ final long currentTime = System.currentTimeMillis();
+ return Formatter.formatFullDate(currentTime) + " " + Formatter.formatTime(currentTime);
+ }
+ });
+ templates.add(new LogTemplate("USER", R.string.init_signature_template_user) {
+
+ @Override
+ public String getValue(final LogContext context) {
+ final Geocache cache = context.getCache();
+ if (cache != null) {
+ final IConnector connector = ConnectorFactory.getConnector(cache);
+ if (connector instanceof ILogin) {
+ return ((ILogin) connector).getUserName();
+ }
+ }
+ return Settings.getUsername();
+ }
+ });
+ templates.add(new LogTemplate("NUMBER", R.string.init_signature_template_number) {
+
+ @Override
+ public String getValue(final LogContext context) {
+ final Geocache cache = context.getCache();
+ if (cache == null) {
+ return StringUtils.EMPTY;
+ }
+
+ int current = 0;
+ final IConnector connector = ConnectorFactory.getConnector(cache);
+ if (connector instanceof ILogin) {
+ current = ((ILogin) connector).getCachesFound();
+ }
+
+ // try updating the login information, if the counter is zero
+ if (current == 0) {
+ if (context.isOffline()) {
+ return StringUtils.EMPTY;
+ }
+ if (connector instanceof ILogin) {
+ ((ILogin) connector).login(null, null);
+ current = ((ILogin) connector).getCachesFound();
+ }
+ }
+
+ if (current >= 0) {
+ return String.valueOf(current + 1);
+ }
+ return StringUtils.EMPTY;
+ }
+ });
+ templates.add(new LogTemplate("OWNER", R.string.init_signature_template_owner) {
+
+ @Override
+ public String getValue(final LogContext context) {
+ final Trackable trackable = context.getTrackable();
+ if (trackable != null) {
+ return trackable.getOwner();
+ }
+ final Geocache cache = context.getCache();
+ if (cache != null) {
+ return cache.getOwnerDisplayName();
+ }
+ return StringUtils.EMPTY;
+ }
+ });
+ templates.add(new LogTemplate("NAME", R.string.init_signature_template_name) {
+ @Override
+ public String getValue(final LogContext context) {
+ final Trackable trackable = context.getTrackable();
+ if (trackable != null) {
+ return trackable.getName();
+ }
+ final Geocache cache = context.getCache();
+ if (cache != null) {
+ return cache.getName();
+ }
+ return StringUtils.EMPTY;
+ }
+ });
+ templates.add(new LogTemplate("URL", R.string.init_signature_template_url) {
+
+ @Override
+ public String getValue(final LogContext context) {
+ final Trackable trackable = context.getTrackable();
+ if (trackable != null) {
+ return trackable.getUrl();
+ }
+ final Geocache cache = context.getCache();
+ if (cache != null) {
+ return StringUtils.defaultString(cache.getUrl());
+ }
+ return StringUtils.EMPTY;
+ }
+ });
+ templates.add(new LogTemplate("LOG", R.string.init_signature_template_log) {
+ @Override
+ public String getValue(final LogContext context) {
+ final LogEntry logEntry = context.getLogEntry();
+ if (logEntry != null) {
+ return logEntry.getDisplayText();
+ }
+ return StringUtils.EMPTY;
+ }
+ });
+ return templates;
+ }
+
+ /**
+ * @return all templates, including the signature template
+ */
+ @NonNull
+ public static List<LogTemplate> getTemplatesWithSignature() {
+ final List<LogTemplate> templates = getTemplatesWithoutSignature();
+ templates.add(new LogTemplate("SIGNATURE", R.string.init_signature) {
+ @Override
+ public String getValue(final LogContext context) {
+ final String nestedTemplate = Settings.getSignature();
+ if (StringUtils.contains(nestedTemplate, "SIGNATURE")) {
+ return "invalid signature template";
+ }
+ return applyTemplates(nestedTemplate, context);
+ }
+ });
+ return templates;
+ }
+
+ @Nullable
+ public static LogTemplate getTemplate(final int itemId) {
+ for (final LogTemplate template : getTemplatesWithSignature()) {
+ if (template.getItemId() == itemId) {
+ return template;
+ }
+ }
+ return null;
+ }
+
+ public static String applyTemplates(@NonNull final String signature, final LogContext context) {
+ String result = signature;
+ for (final LogTemplate template : getTemplatesWithSignature()) {
+ result = template.apply(result, context);
+ }
+ return result;
+ }
+}
diff --git a/main/src/cgeo/geocaching/utils/MapUtils.java b/main/src/cgeo/geocaching/utils/MapUtils.java index 11a705d..9f15bf3 100644 --- a/main/src/cgeo/geocaching/utils/MapUtils.java +++ b/main/src/cgeo/geocaching/utils/MapUtils.java @@ -1,287 +1,293 @@ -package cgeo.geocaching.utils; - -import cgeo.geocaching.Geocache; -import cgeo.geocaching.R; -import cgeo.geocaching.Waypoint; -import cgeo.geocaching.compatibility.Compatibility; -import cgeo.geocaching.enumerations.CacheListType; -import cgeo.geocaching.enumerations.LogType; - -import org.apache.commons.lang3.builder.HashCodeBuilder; - -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.support.annotation.Nullable; -import android.util.SparseArray; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public final class MapUtils { - - // data for overlays - private static final int[][] INSET_RELIABLE = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }; // center, 33x40 / 45x51 / 60x68 / 90x102 / 120x136 - private static final int[][] INSET_TYPE = { { 5, 8, 6, 10 }, { 4, 4, 4, 11 }, { 6, 6, 6, 14 }, { 9, 9, 9, 21 }, { 12, 12, 12, 28 } }; // center, 22x22 / 36x36 - private static final int[][] INSET_OWN = { { 21, 0, 0, 28 }, { 29, 0, 0, 35 }, { 40, 0, 0, 48 }, { 58, 0, 0, 70 }, { 80, 0, 0, 96 } }; // top right, 12x12 / 16x16 / 20x20 / 32x32 / 40x40 - private static final int[][] INSET_FOUND = { { 0, 0, 21, 28 }, { 0, 0, 29, 35 }, { 0, 0, 40, 48 }, { 0, 0, 58, 70 }, { 0, 0, 80, 96 } }; // top left, 12x12 / 16x16 / 20x20 / 32x32 / 40x40 - private static final int[][] INSET_USERMODIFIEDCOORDS = { { 21, 28, 0, 0 }, { 29, 35, 0, 0 }, { 40, 48, 0, 0 }, { 58, 70, 0, 0 }, { 80, 96, 0, 0 } }; // bottom right, 12x12 / 16x16 / 20x20 / 32x32 / 40x40 - private static final int[][] INSET_PERSONALNOTE = { { 0, 28, 21, 0 }, { 0, 35, 29, 0 }, { 0, 48, 40, 0 }, { 0, 70, 58, 0 }, { 0, 96, 80, 0 } }; // bottom left, 12x12 / 16x16 / 20x20 / 32x32 / 40x40 - - private static final SparseArray<LayerDrawable> overlaysCache = new SparseArray<>(); - - private MapUtils() { - // Do not instantiate - } - - /** - * Obtain the drawable for a given cache, with background circle. - * - * @param res - * the resources to use - * @param cache - * the cache to build the drawable for - * @return - * a drawable representing the current cache status - */ - public static LayerDrawable getCacheMarker(final Resources res, final Geocache cache) { - return getCacheMarker(res, cache, null); - } - - /** - * Obtain the drawable for a given cache. - * Return a drawable from the cache, if a similar drawable was already generated. - * - * cacheListType should be Null if the requesting activity is Map. - * - * @param res - * the resources to use - * @param cache - * the cache to build the drawable for - * @param cacheListType - * the current CacheListType or Null - * @return - * a drawable representing the current cache status - */ - public static LayerDrawable getCacheMarker(final Resources res, final Geocache cache, @Nullable final CacheListType cacheListType) { - final int hashcode = new HashCodeBuilder() - .append(cache.isReliableLatLon()) - .append(cache.getType().id) - .append(cache.isDisabled() || cache.isArchived()) - .append(cache.getMapMarkerId()) - .append(cache.isOwner()) - .append(cache.isFound()) - .append(cache.hasUserModifiedCoords()) - .append(cache.getPersonalNote()) - .append(cache.isLogOffline()) - .append(cache.getListId() > 0) - .append(cache.getOfflineLogType()) - .append(showBackground(cacheListType)) - .append(showFloppyOverlay(cacheListType)) - .toHashCode(); - - synchronized (overlaysCache) { - LayerDrawable drawable = overlaysCache.get(hashcode); - if (drawable == null) { - drawable = createCacheMarker(res, cache, cacheListType); - overlaysCache.put(hashcode, drawable); - } - return drawable; - } - } - - /** - * Obtain the drawable for a given waypoint. - * Return a drawable from the cache, if a similar drawable was already generated. - * - * @param res - * the resources to use - * @param waypoint - * the waypoint to build the drawable for - * @return - * a drawable representing the current waypoint status - */ - public static LayerDrawable getWaypointMarker(final Resources res, final Waypoint waypoint) { - final int hashcode = new HashCodeBuilder() - .append(waypoint.isVisited()) - .append(waypoint.getWaypointType().id) - .toHashCode(); - - synchronized (overlaysCache) { - LayerDrawable drawable = overlaysCache.get(hashcode); - if (drawable == null) { - drawable = createWaypointMarker(res, waypoint); - overlaysCache.put(hashcode, drawable); - } - return drawable; - } - } - - /** - * Build the drawable for a given waypoint. - * - * @param res - * the resources to use - * @param waypoint - * the waypoint to build the drawable for - * @return - * a drawable representing the current waypoint status - */ - private static LayerDrawable createWaypointMarker(final Resources res, final Waypoint waypoint) { - final Drawable marker = Compatibility.getDrawable(res, !waypoint.isVisited() ? R.drawable.marker : R.drawable.marker_transparent); - final Drawable[] layers = { - marker, - Compatibility.getDrawable(res, waypoint.getWaypointType().markerId) - }; - final LayerDrawable drawable = new LayerDrawable(layers); - final int resolution = calculateResolution(marker); - drawable.setLayerInset(1, INSET_TYPE[resolution][0], INSET_TYPE[resolution][1], INSET_TYPE[resolution][2], INSET_TYPE[resolution][3]); - return drawable; - } - - /** - * Clear the cache of drawable items. - */ - public static void clearCachedItems() { - synchronized (overlaysCache) { - overlaysCache.clear(); - } - } - - /** - * Build the drawable for a given cache. - * - * @param res - * the resources to use - * @param cache - * the cache to build the drawable for - * @param cacheListType - * the current CacheListType or Null - * @return - * a drawable representing the current cache status - */ - private static LayerDrawable createCacheMarker(final Resources res, final Geocache cache, @Nullable final CacheListType cacheListType) { - // Set initial capacities to the maximum of layers and insets to avoid dynamic reallocation - final List<Drawable> layers = new ArrayList<>(9); - final List<int[]> insets = new ArrayList<>(8); - - // background: disabled or not - final Drawable marker = Compatibility.getDrawable(res, cache.getMapMarkerId()); - // Show the background circle only on map - if (showBackground(cacheListType)) { - layers.add(marker); - } - final int resolution = calculateResolution(marker); - // reliable or not - if (!cache.isReliableLatLon()) { - insets.add(INSET_RELIABLE[resolution]); - layers.add(Compatibility.getDrawable(res, R.drawable.marker_notreliable)); - } - // cache type - layers.add(Compatibility.getDrawable(res, cache.getType().markerId)); - insets.add(INSET_TYPE[resolution]); - // own - if (cache.isOwner()) { - layers.add(Compatibility.getDrawable(res, R.drawable.marker_own)); - insets.add(INSET_OWN[resolution]); - // if not, checked if stored - } else if (cache.getListId() > 0 && showFloppyOverlay(cacheListType)) { - layers.add(Compatibility.getDrawable(res, R.drawable.marker_stored)); - insets.add(INSET_OWN[resolution]); - } - // found - if (cache.isFound()) { - layers.add(Compatibility.getDrawable(res, R.drawable.marker_found)); - insets.add(INSET_FOUND[resolution]); - // if not, perhaps logged offline - } else if (cache.isLogOffline()) { - final LogType offlineLogType = cache.getOfflineLogType(); - if (offlineLogType == null) { - // Default, backward compatible - layers.add(Compatibility.getDrawable(res, R.drawable.marker_found_offline)); - } else { - layers.add(Compatibility.getDrawable(res, offlineLogType.getOfflineLogOverlay())); - } - insets.add(INSET_FOUND[resolution]); - } - // user modified coords - if (cache.hasUserModifiedCoords()) { - layers.add(Compatibility.getDrawable(res, R.drawable.marker_usermodifiedcoords)); - insets.add(driftBottomItems(INSET_USERMODIFIEDCOORDS, resolution, cacheListType)); - } - // personal note - if (cache.getPersonalNote() != null) { - layers.add(Compatibility.getDrawable(res, R.drawable.marker_personalnote)); - insets.add(driftBottomItems(INSET_PERSONALNOTE, resolution, cacheListType)); - } - - final LayerDrawable ld = new LayerDrawable(layers.toArray(new Drawable[layers.size()])); - - int index = showBackground(cacheListType) ? 1 : 0; - for (final int[] inset : insets) { - ld.setLayerInset(index++, inset[0], inset[1], inset[2], inset[3]); - } - - return ld; - } - - /** - * Get the resolution index used for positionning the overlays elements. - * - * @param marker - * The Drawable reference - * @return - * an index for the overlays positions - */ - private static int calculateResolution(final Drawable marker) { - return marker.getIntrinsicWidth() > 40 ? (marker.getIntrinsicWidth() > 50 ? (marker.getIntrinsicWidth() > 70 ? (marker.getIntrinsicWidth() > 100 ? 4 : 3) : 2) : 1) : 0; - } - - /** - * Calculate a new position for the bottom line overlay items, when there is no background circle. - * - * @param inset - * Original inset position - * @param resolution - * The current item resolution - * @param cacheListType - * The current CacheListType - * @return - * The new drifted inset position - */ - private static int[] driftBottomItems(final int[][] inset, final int resolution, @Nullable final CacheListType cacheListType) { - // Do not drift in when background is displayed - if (showBackground(cacheListType)) { - return inset[resolution]; - } - final int[] newPosition = Arrays.copyOf(inset[resolution], 4); - newPosition[1] -= INSET_TYPE[resolution][3]; - newPosition[3] += INSET_TYPE[resolution][3]; - return newPosition; - } - - /** - * Conditionnal expression to choose if we need the background circle or not. - * - * @param cacheListType - * The cache list currently used - * @return - * True if the background circle should be displayed - */ - private static boolean showBackground(final CacheListType cacheListType) { - return cacheListType == null; - } - - /** - * Conditionnal expression to choose if we need the floppy overlay or not. - * - * @param cacheListType - * The cache list currently used - * @return - * True if the floppy overlay should be displayed - */ - private static boolean showFloppyOverlay(final CacheListType cacheListType) { - return cacheListType == null || cacheListType != CacheListType.OFFLINE; - } -} +package cgeo.geocaching.utils;
+
+import cgeo.geocaching.Geocache;
+import cgeo.geocaching.R;
+import cgeo.geocaching.Waypoint;
+import cgeo.geocaching.compatibility.Compatibility;
+import cgeo.geocaching.enumerations.CacheListType;
+import cgeo.geocaching.enumerations.LogType;
+
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.eclipse.jdt.annotation.NonNull;
+
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.support.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public final class MapUtils {
+
+ // data for overlays
+ private static final int[][] INSET_RELIABLE = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }; // center, 33x40 / 45x51 / 60x68 / 90x102 / 120x136
+ private static final int[][] INSET_TYPE = { { 5, 8, 6, 10 }, { 4, 4, 4, 11 }, { 6, 6, 6, 14 }, { 9, 9, 9, 21 }, { 12, 12, 12, 28 } }; // center, 22x22 / 36x36
+ private static final int[][] INSET_OWN = { { 21, 0, 0, 28 }, { 29, 0, 0, 35 }, { 40, 0, 0, 48 }, { 58, 0, 0, 70 }, { 80, 0, 0, 96 } }; // top right, 12x12 / 16x16 / 20x20 / 32x32 / 40x40
+ private static final int[][] INSET_FOUND = { { 0, 0, 21, 28 }, { 0, 0, 29, 35 }, { 0, 0, 40, 48 }, { 0, 0, 58, 70 }, { 0, 0, 80, 96 } }; // top left, 12x12 / 16x16 / 20x20 / 32x32 / 40x40
+ private static final int[][] INSET_USERMODIFIEDCOORDS = { { 21, 28, 0, 0 }, { 29, 35, 0, 0 }, { 40, 48, 0, 0 }, { 58, 70, 0, 0 }, { 80, 96, 0, 0 } }; // bottom right, 12x12 / 16x16 / 20x20 / 32x32 / 40x40
+ private static final int[][] INSET_PERSONALNOTE = { { 0, 28, 21, 0 }, { 0, 35, 29, 0 }, { 0, 48, 40, 0 }, { 0, 70, 58, 0 }, { 0, 96, 80, 0 } }; // bottom left, 12x12 / 16x16 / 20x20 / 32x32 / 40x40
+
+ private static final SparseArray<LayerDrawable> overlaysCache = new SparseArray<>();
+
+ private MapUtils() {
+ // Do not instantiate
+ }
+
+ /**
+ * Obtain the drawable for a given cache, with background circle.
+ *
+ * @param res
+ * the resources to use
+ * @param cache
+ * the cache to build the drawable for
+ * @return
+ * a drawable representing the current cache status
+ */
+ @NonNull
+ public static LayerDrawable getCacheMarker(final Resources res, final Geocache cache) {
+ return getCacheMarker(res, cache, null);
+ }
+
+ /**
+ * Obtain the drawable for a given cache.
+ * Return a drawable from the cache, if a similar drawable was already generated.
+ *
+ * cacheListType should be Null if the requesting activity is Map.
+ *
+ * @param res
+ * the resources to use
+ * @param cache
+ * the cache to build the drawable for
+ * @param cacheListType
+ * the current CacheListType or Null
+ * @return
+ * a drawable representing the current cache status
+ */
+ @NonNull
+ public static LayerDrawable getCacheMarker(final Resources res, final Geocache cache, @Nullable final CacheListType cacheListType) {
+ final int hashcode = new HashCodeBuilder()
+ .append(cache.isReliableLatLon())
+ .append(cache.getType().id)
+ .append(cache.isDisabled() || cache.isArchived())
+ .append(cache.getMapMarkerId())
+ .append(cache.isOwner())
+ .append(cache.isFound())
+ .append(cache.hasUserModifiedCoords())
+ .append(cache.getPersonalNote())
+ .append(cache.isLogOffline())
+ .append(cache.getListId() > 0)
+ .append(cache.getOfflineLogType())
+ .append(showBackground(cacheListType))
+ .append(showFloppyOverlay(cacheListType))
+ .toHashCode();
+
+ synchronized (overlaysCache) {
+ LayerDrawable drawable = overlaysCache.get(hashcode);
+ if (drawable == null) {
+ drawable = createCacheMarker(res, cache, cacheListType);
+ overlaysCache.put(hashcode, drawable);
+ }
+ return drawable;
+ }
+ }
+
+ /**
+ * Obtain the drawable for a given waypoint.
+ * Return a drawable from the cache, if a similar drawable was already generated.
+ *
+ * @param res
+ * the resources to use
+ * @param waypoint
+ * the waypoint to build the drawable for
+ * @return
+ * a drawable representing the current waypoint status
+ */
+ @NonNull
+ public static LayerDrawable getWaypointMarker(final Resources res, final Waypoint waypoint) {
+ final int hashcode = new HashCodeBuilder()
+ .append(waypoint.isVisited())
+ .append(waypoint.getWaypointType().id)
+ .toHashCode();
+
+ synchronized (overlaysCache) {
+ LayerDrawable drawable = overlaysCache.get(hashcode);
+ if (drawable == null) {
+ drawable = createWaypointMarker(res, waypoint);
+ overlaysCache.put(hashcode, drawable);
+ }
+ return drawable;
+ }
+ }
+
+ /**
+ * Build the drawable for a given waypoint.
+ *
+ * @param res
+ * the resources to use
+ * @param waypoint
+ * the waypoint to build the drawable for
+ * @return
+ * a drawable representing the current waypoint status
+ */
+ @NonNull
+ private static LayerDrawable createWaypointMarker(final Resources res, final Waypoint waypoint) {
+ final Drawable marker = Compatibility.getDrawable(res, !waypoint.isVisited() ? R.drawable.marker : R.drawable.marker_transparent);
+ final Drawable[] layers = {
+ marker,
+ Compatibility.getDrawable(res, waypoint.getWaypointType().markerId)
+ };
+ final LayerDrawable drawable = new LayerDrawable(layers);
+ final int resolution = calculateResolution(marker);
+ drawable.setLayerInset(1, INSET_TYPE[resolution][0], INSET_TYPE[resolution][1], INSET_TYPE[resolution][2], INSET_TYPE[resolution][3]);
+ return drawable;
+ }
+
+ /**
+ * Clear the cache of drawable items.
+ */
+ public static void clearCachedItems() {
+ synchronized (overlaysCache) {
+ overlaysCache.clear();
+ }
+ }
+
+ /**
+ * Build the drawable for a given cache.
+ *
+ * @param res
+ * the resources to use
+ * @param cache
+ * the cache to build the drawable for
+ * @param cacheListType
+ * the current CacheListType or Null
+ * @return
+ * a drawable representing the current cache status
+ */
+ @NonNull
+ private static LayerDrawable createCacheMarker(final Resources res, final Geocache cache, @Nullable final CacheListType cacheListType) {
+ // Set initial capacities to the maximum of layers and insets to avoid dynamic reallocation
+ final List<Drawable> layers = new ArrayList<>(9);
+ final List<int[]> insets = new ArrayList<>(8);
+
+ // background: disabled or not
+ final Drawable marker = Compatibility.getDrawable(res, cache.getMapMarkerId());
+ // Show the background circle only on map
+ if (showBackground(cacheListType)) {
+ layers.add(marker);
+ }
+ final int resolution = calculateResolution(marker);
+ // reliable or not
+ if (!cache.isReliableLatLon()) {
+ insets.add(INSET_RELIABLE[resolution]);
+ layers.add(Compatibility.getDrawable(res, R.drawable.marker_notreliable));
+ }
+ // cache type
+ layers.add(Compatibility.getDrawable(res, cache.getType().markerId));
+ insets.add(INSET_TYPE[resolution]);
+ // own
+ if (cache.isOwner()) {
+ layers.add(Compatibility.getDrawable(res, R.drawable.marker_own));
+ insets.add(INSET_OWN[resolution]);
+ // if not, checked if stored
+ } else if (cache.getListId() > 0 && showFloppyOverlay(cacheListType)) {
+ layers.add(Compatibility.getDrawable(res, R.drawable.marker_stored));
+ insets.add(INSET_OWN[resolution]);
+ }
+ // found
+ if (cache.isFound()) {
+ layers.add(Compatibility.getDrawable(res, R.drawable.marker_found));
+ insets.add(INSET_FOUND[resolution]);
+ // if not, perhaps logged offline
+ } else if (cache.isLogOffline()) {
+ final LogType offlineLogType = cache.getOfflineLogType();
+ if (offlineLogType == null) {
+ // Default, backward compatible
+ layers.add(Compatibility.getDrawable(res, R.drawable.marker_found_offline));
+ } else {
+ layers.add(Compatibility.getDrawable(res, offlineLogType.getOfflineLogOverlay()));
+ }
+ insets.add(INSET_FOUND[resolution]);
+ }
+ // user modified coords
+ if (cache.hasUserModifiedCoords()) {
+ layers.add(Compatibility.getDrawable(res, R.drawable.marker_usermodifiedcoords));
+ insets.add(driftBottomItems(INSET_USERMODIFIEDCOORDS, resolution, cacheListType));
+ }
+ // personal note
+ if (cache.getPersonalNote() != null) {
+ layers.add(Compatibility.getDrawable(res, R.drawable.marker_personalnote));
+ insets.add(driftBottomItems(INSET_PERSONALNOTE, resolution, cacheListType));
+ }
+
+ final LayerDrawable ld = new LayerDrawable(layers.toArray(new Drawable[layers.size()]));
+
+ int index = showBackground(cacheListType) ? 1 : 0;
+ for (final int[] inset : insets) {
+ ld.setLayerInset(index++, inset[0], inset[1], inset[2], inset[3]);
+ }
+
+ return ld;
+ }
+
+ /**
+ * Get the resolution index used for positioning the overlays elements.
+ *
+ * @param marker
+ * The Drawable reference
+ * @return
+ * an index for the overlays positions
+ */
+ private static int calculateResolution(final Drawable marker) {
+ return marker.getIntrinsicWidth() > 40 ? (marker.getIntrinsicWidth() > 50 ? (marker.getIntrinsicWidth() > 70 ? (marker.getIntrinsicWidth() > 100 ? 4 : 3) : 2) : 1) : 0;
+ }
+
+ /**
+ * Calculate a new position for the bottom line overlay items, when there is no background circle.
+ *
+ * @param inset
+ * Original inset position
+ * @param resolution
+ * The current item resolution
+ * @param cacheListType
+ * The current CacheListType
+ * @return
+ * The new drifted inset position
+ */
+ private static int[] driftBottomItems(final int[][] inset, final int resolution, @Nullable final CacheListType cacheListType) {
+ // Do not drift in when background is displayed
+ if (showBackground(cacheListType)) {
+ return inset[resolution];
+ }
+ final int[] newPosition = Arrays.copyOf(inset[resolution], 4);
+ newPosition[1] -= INSET_TYPE[resolution][3];
+ newPosition[3] += INSET_TYPE[resolution][3];
+ return newPosition;
+ }
+
+ /**
+ * Conditional expression to choose if we need the background circle or not.
+ *
+ * @param cacheListType
+ * The cache list currently used
+ * @return
+ * True if the background circle should be displayed
+ */
+ private static boolean showBackground(final CacheListType cacheListType) {
+ return cacheListType == null;
+ }
+
+ /**
+ * Conditional expression to choose if we need the floppy overlay or not.
+ *
+ * @param cacheListType
+ * The cache list currently used
+ * @return
+ * True if the floppy overlay should be displayed
+ */
+ private static boolean showFloppyOverlay(final CacheListType cacheListType) {
+ return cacheListType == null || cacheListType != CacheListType.OFFLINE;
+ }
+}
diff --git a/main/src/cgeo/geocaching/utils/MatcherWrapper.java b/main/src/cgeo/geocaching/utils/MatcherWrapper.java index 733a18e..b3a361b 100644 --- a/main/src/cgeo/geocaching/utils/MatcherWrapper.java +++ b/main/src/cgeo/geocaching/utils/MatcherWrapper.java @@ -1,12 +1,13 @@ package cgeo.geocaching.utils; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.util.regex.Matcher; import java.util.regex.Pattern; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * Wrapper around the regex {@link Matcher} class. This implementation optimizes the memory usage of the matched * Strings. @@ -46,6 +47,7 @@ public class MatcherWrapper { * Do not change this method, even if Findbugs and other tools will report a violation for that line! * */ + @Nullable @SuppressFBWarnings("DM_STRING_CTOR") private static String newString(final String input) { if (input == null) { |