termux-app/termux-shared/src/main/java/com/termux/shared/settings/properties/SharedProperties.java

462 lines
20 KiB
Java
Raw Normal View History

package com.termux.shared.settings.properties;
2021-03-11 13:22:11 +00:00
import android.content.Context;
import android.widget.Toast;
import com.google.common.collect.ImmutableBiMap;
2021-03-11 13:22:11 +00:00
import com.google.common.primitives.Primitives;
import com.termux.shared.logger.Logger;
2021-03-11 13:22:11 +00:00
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An implementation similar to android's {@link android.content.SharedPreferences} interface for
* reading and writing to and from ".properties" files which also maintains an in-memory cache for
* the key/value pairs when an instance object is used. Operations are done under
* synchronization locks and should be thread safe.
2021-03-11 13:22:11 +00:00
*
* If {@link SharedProperties} instance object is used, then two types of in-memory cache maps are
* maintained, one for the literal {@link String} values found in the file for the keys and an
* additional one that stores (near) primitive {@link Object} values for internal use by the caller.
*
* The {@link SharedProperties} also provides static functions that can be used to read properties
* from files or individual key values or even their internal values. An automatic mapping to a
2021-04-06 12:17:12 +00:00
* boolean as internal value can also be done. An in-memory cache is not maintained, nor are locks used.
2021-03-11 13:22:11 +00:00
*
* This currently only has read support, write support can/will be added later if needed. Check android's
* SharedPreferencesImpl class for reference implementation.
*
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/app/SharedPreferencesImpl.java
*/
public class SharedProperties {
/**
* The {@link Properties} object that maintains an in-memory cache of values loaded from the
* {@link #mPropertiesFile} file. The key/value pairs are of any keys that are found in the file
* against their literal values in the file.
2021-03-11 13:22:11 +00:00
*/
private Properties mProperties;
/**
* The {@link HashMap<>} object that maintains an in-memory cache of internal values for the values
* loaded from the {@link #mPropertiesFile} file. The key/value pairs are of any keys defined by
* {@link #mPropertiesList} that are found in the file against their internal {@link Object} values
* returned by the call to
* {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context, String, String)} interface.
*/
private Map<String, Object> mMap;
private final Context mContext;
private final File mPropertiesFile;
private final Set<String> mPropertiesList;
private final SharedPropertiesParser mSharedPropertiesParser;
private final Object mLock = new Object();
/** Defines the bidirectional map for boolean values and their internal values */
public static final ImmutableBiMap<String, Boolean> MAP_GENERIC_BOOLEAN =
new ImmutableBiMap.Builder<String, Boolean>()
.put("true", true)
.put("false", false)
.build();
/** Defines the bidirectional map for inverted boolean values and their internal values */
public static final ImmutableBiMap<String, Boolean> MAP_GENERIC_INVERTED_BOOLEAN =
new ImmutableBiMap.Builder<String, Boolean>()
.put("true", false)
.put("false", true)
.build();
Implement GUI based Termux settings manager and a centralized logging framework The settings activity can be accessed by long pressing on terminal view and selecting "Settings" from the popup shown. It uses the Android's Preference framework. Currently only debugging preferences to set log level and enabling terminal view key logging are provided. The Preference framework by default uses the keys set in `app:key` attribute in the respective preferences XML file to store the values in the default `SharedPreferences` file of the app. However, since we rely on `TermuxPreferenceConstants` and `TermuxPropertyConstants` classes to define key names so that they can be easily shared between termux and its plugin apps, we provide our own `PreferenceDataStore` for storing key/value pairs. The key name in the XML file can optionally be the same. Check `DebuggingPreferencesFragment` class for a sample. Each new preference category fragment should be added to `app/settings/` with its data store. This commit may allow support to be added for modifying `termux.properties` file directly from the UI but that requires more work, since writing to property files with comments require in-place modification. The `Logger` class provides various static functions for logging that should be used from now on instead of directly calling android `Log.*` functions. The log level is automatically loaded from shared preferences at application startup via `TermuxApplication` and set in the static `Logger.CURRENT_LOG_LEVEL` variable. Changing the log level through the settings activity also changes the log level immediately. The 4 supported log levels are: - LOG_LEVEL_OFF which will log nothing. - LOG_LEVEL_NORMAL which will start logging error, warn and info messages and stacktraces. - LOG_LEVEL_DEBUG which will start logging debug messages. - LOG_LEVEL_VERBOSE which will start logging verbose messages. The default log level is `LOG_LEVEL_NORMAL` which will not log debug or verbose messages. Contributors can add useful log entries at those levels where ever they feel is appropriate so that it allows users and devs to more easily help solve issues or find bugs, specially without having to recompile termux after having to manually add general log entries to the source. DO NOT log data that may have private info of users like command arguments at log levels below debug, like `BackgroundJob` was doing previously. Logging to file support may be added later, will require log file rotation support and storage permissions.
2021-03-13 11:49:29 +00:00
private static final String LOG_TAG = "SharedProperties";
2021-03-11 13:22:11 +00:00
/**
* Constructor for the SharedProperties class.
*
* @param context The Context for operations.
* @param propertiesFile The {@link File} object to load properties from.
* @param propertiesList The {@link Set<String>} object that defined which properties to load.
* If this is set to {@code null}, then all properties that exist in
* {@code propertiesFile} will be read by {@link #loadPropertiesFromDisk()}
* @param sharedPropertiesParser The implementation of the {@link SharedPropertiesParser} interface.
2021-03-11 13:22:11 +00:00
*/
public SharedProperties(@Nonnull Context context, @Nullable File propertiesFile, Set<String> propertiesList, @Nonnull SharedPropertiesParser sharedPropertiesParser) {
2021-03-11 13:22:11 +00:00
mContext = context;
mPropertiesFile = propertiesFile;
mPropertiesList = propertiesList;
mSharedPropertiesParser = sharedPropertiesParser;
mProperties = new Properties();
mMap = new HashMap<>();
2021-03-11 13:22:11 +00:00
}
/**
* Load the properties defined by {@link #mPropertiesList} or all properties if its {@code null}
* from the {@link #mPropertiesFile} file to update the {@link #mProperties} and {@link #mMap}
* in-memory cache.
2021-03-11 13:22:11 +00:00
* Properties are not loading automatically when constructor is called and must be manually called.
*/
public void loadPropertiesFromDisk() {
synchronized (mLock) {
// Get properties from mPropertiesFile
Properties properties = getProperties(false);
// We still need to load default values into mMap, so we assume no properties defined if
// reading from mPropertiesFile failed
if (properties == null)
properties = new Properties();
HashMap<String, Object> map = new HashMap<>();
2021-03-11 13:22:11 +00:00
Properties newProperties = new Properties();
Set<String> propertiesList = mPropertiesList;
2021-04-06 12:17:12 +00:00
if (propertiesList == null)
propertiesList = properties.stringPropertyNames();
2021-03-11 13:22:11 +00:00
String value;
Object internalValue;
for (String key : propertiesList) {
2021-03-11 13:22:11 +00:00
value = properties.getProperty(key); // value will be null if key does not exist in propertiesFile
Implement GUI based Termux settings manager and a centralized logging framework The settings activity can be accessed by long pressing on terminal view and selecting "Settings" from the popup shown. It uses the Android's Preference framework. Currently only debugging preferences to set log level and enabling terminal view key logging are provided. The Preference framework by default uses the keys set in `app:key` attribute in the respective preferences XML file to store the values in the default `SharedPreferences` file of the app. However, since we rely on `TermuxPreferenceConstants` and `TermuxPropertyConstants` classes to define key names so that they can be easily shared between termux and its plugin apps, we provide our own `PreferenceDataStore` for storing key/value pairs. The key name in the XML file can optionally be the same. Check `DebuggingPreferencesFragment` class for a sample. Each new preference category fragment should be added to `app/settings/` with its data store. This commit may allow support to be added for modifying `termux.properties` file directly from the UI but that requires more work, since writing to property files with comments require in-place modification. The `Logger` class provides various static functions for logging that should be used from now on instead of directly calling android `Log.*` functions. The log level is automatically loaded from shared preferences at application startup via `TermuxApplication` and set in the static `Logger.CURRENT_LOG_LEVEL` variable. Changing the log level through the settings activity also changes the log level immediately. The 4 supported log levels are: - LOG_LEVEL_OFF which will log nothing. - LOG_LEVEL_NORMAL which will start logging error, warn and info messages and stacktraces. - LOG_LEVEL_DEBUG which will start logging debug messages. - LOG_LEVEL_VERBOSE which will start logging verbose messages. The default log level is `LOG_LEVEL_NORMAL` which will not log debug or verbose messages. Contributors can add useful log entries at those levels where ever they feel is appropriate so that it allows users and devs to more easily help solve issues or find bugs, specially without having to recompile termux after having to manually add general log entries to the source. DO NOT log data that may have private info of users like command arguments at log levels below debug, like `BackgroundJob` was doing previously. Logging to file support may be added later, will require log file rotation support and storage permissions.
2021-03-13 11:49:29 +00:00
Logger.logDebug(LOG_TAG, key + " : " + value);
2021-03-11 13:22:11 +00:00
// Call the {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context,String,String)}
// interface method to get the internal value to store in the {@link #mMap}.
2021-03-11 13:22:11 +00:00
internalValue = mSharedPropertiesParser.getInternalPropertyValueFromValue(mContext, key, value);
// If the internal value was successfully added to map, then also add value to newProperties
// We only store values in-memory defined by propertiesList
2021-03-11 13:22:11 +00:00
if (putToMap(map, key, internalValue)) { // null internalValue will be put into map
putToProperties(newProperties, key, value); // null value will **not** be into properties
}
}
mMap = map;
mProperties = newProperties;
}
}
/**
* Get the {@link Properties} object for the {@link #mPropertiesFile}. The {@link Properties}
* object will also contain properties not defined by the {@link #mPropertiesList} if cache
* value is {@code false}.
*
* @param cached If {@code true}, then the {@link #mProperties} in-memory cache is returned. Otherwise
* the {@link Properties} object is directly read from the {@link #mPropertiesFile}.
* @return Returns the {@link Properties} object if read from file, otherwise a copy of {@link #mProperties}.
*/
public Properties getProperties(boolean cached) {
synchronized (mLock) {
if (cached) {
if (mProperties == null) mProperties = new Properties();
return getPropertiesCopy(mProperties);
} else {
return getPropertiesFromFile(mContext, mPropertiesFile);
}
}
}
/**
* Get the {@link String} value for the key passed from the {@link #mPropertiesFile}.
*
* @param key The key to read from the {@link Properties} object.
* @param cached If {@code true}, then the value is returned from the {@link #mProperties} in-memory cache.
* Otherwise the {@link Properties} object is read directly from the {@link #mPropertiesFile}
* and value is returned from it against the key.
* @return Returns the {@link String} object. This will be {@code null} if key is not found.
*/
public String getProperty(String key, boolean cached) {
synchronized (mLock) {
return (String) getProperties(cached).get(key);
}
}
/**
* Get the {@link #mMap} object for the {@link #mPropertiesFile}. A call to
* {@link #loadPropertiesFromDisk()} must be made before this.
*
* @return Returns a copy of {@link #mMap} object.
*/
public Map<String, Object> getInternalProperties() {
synchronized (mLock) {
if (mMap == null) mMap = new HashMap<>();
return getMapCopy(mMap);
}
}
/**
* Get the internal {@link Object} value for the key passed from the {@link #mPropertiesFile}.
* The value is returned from the {@link #mMap} in-memory cache, so a call to
* {@link #loadPropertiesFromDisk()} must be made before this.
*
* @param key The key to read from the {@link #mMap} object.
* @return Returns the {@link Object} object. This will be {@code null} if key is not found or
* if object was {@code null}. Use {@link HashMap#containsKey(Object)} to detect the later.
* situation.
*/
public Object getInternalProperty(String key) {
synchronized (mLock) {
// null keys are not allowed to be stored in mMap
if (key != null)
return getInternalProperties().get(key);
else
return null;
}
}
/**
* A static function to get the {@link Properties} object for the propertiesFile. A lock is not
* taken when this function is called.
*
* @param context The {@link Context} to use to show a flash if an exception is raised while
* reading the file. If context is {@code null}, then flash will not be shown.
* @param propertiesFile The {@link File} to read the {@link Properties} from.
* @return Returns the {@link Properties} object. It will be {@code null} if an exception is
* raised while reading the file.
*/
public static Properties getPropertiesFromFile(Context context, File propertiesFile) {
Properties properties = new Properties();
if (propertiesFile == null) {
Logger.logWarn(LOG_TAG, "Not loading properties since file is null");
return properties;
}
try {
try (FileInputStream in = new FileInputStream(propertiesFile)) {
Logger.logVerbose(LOG_TAG, "Loading properties from \"" + propertiesFile.getAbsolutePath() + "\" file");
properties.load(new InputStreamReader(in, StandardCharsets.UTF_8));
}
} catch (Exception e) {
2021-04-06 12:17:12 +00:00
if (context != null)
Toast.makeText(context, "Could not open properties file \"" + propertiesFile.getAbsolutePath() + "\": " + e.getMessage(), Toast.LENGTH_LONG).show();
Logger.logStackTraceWithMessage(LOG_TAG, "Error loading properties file \"" + propertiesFile.getAbsolutePath() + "\"", e);
return null;
}
return properties;
}
/**
* A static function to get the {@link String} value for the {@link Properties} key read from
* the propertiesFile file.
*
* @param context The {@link Context} for the {@link #getPropertiesFromFile(Context,File)} call.
* @param propertiesFile The {@link File} to read the {@link Properties} from.
* @param key The key to read.
* @param def The default value.
* @return Returns the {@link String} object. This will be {@code null} if key is not found.
*/
public static String getProperty(Context context, File propertiesFile, String key, String def) {
return (String) getDefaultIfNull(getDefaultIfNull(getPropertiesFromFile(context, propertiesFile), new Properties()).get(key), def);
}
/**
* A static function to get the internal {@link Object} value for the {@link String} value for
* the {@link Properties} key read from the propertiesFile file.
*
* @param context The {@link Context} for the {@link #getPropertiesFromFile(Context,File)} call.
* @param propertiesFile The {@link File} to read the {@link Properties} from.
* @param key The key to read.
* @param sharedPropertiesParser The implementation of the {@link SharedPropertiesParser} interface.
* @return Returns the {@link String} Object returned by the call to
* {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context,String,String)}.
*/
public static Object getInternalProperty(Context context, File propertiesFile, String key, @Nonnull SharedPropertiesParser sharedPropertiesParser) {
String value = (String) getDefaultIfNull(getPropertiesFromFile(context, propertiesFile), new Properties()).get(key);
// Call the {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context,String,String)}
// interface method to get the internal value to return.
return sharedPropertiesParser.getInternalPropertyValueFromValue(context, key, value);
}
/**
* A static function to check if the value is {@code true} for {@link Properties} key read from
* the propertiesFile file.
*
* @param context The {@link Context} for the {@link #getPropertiesFromFile(Context,File)}call.
* @param propertiesFile The {@link File} to read the {@link Properties} from.
* @param key The key to read.
* @return Returns the {@code true} if the {@link Properties} key {@link String} value equals "true",
* regardless of case. If the key does not exist in the file or does not equal "true", then
* {@code false} will be returned.
*/
public static boolean isPropertyValueTrue(Context context, File propertiesFile, String key) {
return (boolean) getBooleanValueForStringValue((String) getProperty(context, propertiesFile, key, null), false);
}
/**
* A static function to check if the value is {@code false} for {@link Properties} key read from
* the propertiesFile file.
*
* @param context The {@link Context} for the {@link #getPropertiesFromFile(Context,File)} call.
* @param propertiesFile The {@link File} to read the {@link Properties} from.
* @param key The key to read.
* @return Returns the {@code true} if the {@link Properties} key {@link String} value equals "false",
* regardless of case. If the key does not exist in the file or does not equal "false", then
* {@code true} will be returned.
*/
public static boolean isPropertyValueFalse(Context context, File propertiesFile, String key) {
return (boolean) getInvertedBooleanValueForStringValue((String) getProperty(context, propertiesFile, key, null), true);
}
2021-03-11 13:22:11 +00:00
/**
* Put a value in a {@link #mMap}.
* The key cannot be {@code null}.
* Only {@code null}, primitive or their wrapper classes or String class objects are allowed to be added to
* the map, although this limitation may be changed.
*
* @param map The {@link Map} object to add value to.
* @param key The key for which to add the value to the map.
* @param value The {@link Object} to add to the map.
* @return Returns {@code true} if value was successfully added, otherwise {@code false}.
*/
public static boolean putToMap(HashMap<String, Object> map, String key, Object value) {
if (map == null) {
Implement GUI based Termux settings manager and a centralized logging framework The settings activity can be accessed by long pressing on terminal view and selecting "Settings" from the popup shown. It uses the Android's Preference framework. Currently only debugging preferences to set log level and enabling terminal view key logging are provided. The Preference framework by default uses the keys set in `app:key` attribute in the respective preferences XML file to store the values in the default `SharedPreferences` file of the app. However, since we rely on `TermuxPreferenceConstants` and `TermuxPropertyConstants` classes to define key names so that they can be easily shared between termux and its plugin apps, we provide our own `PreferenceDataStore` for storing key/value pairs. The key name in the XML file can optionally be the same. Check `DebuggingPreferencesFragment` class for a sample. Each new preference category fragment should be added to `app/settings/` with its data store. This commit may allow support to be added for modifying `termux.properties` file directly from the UI but that requires more work, since writing to property files with comments require in-place modification. The `Logger` class provides various static functions for logging that should be used from now on instead of directly calling android `Log.*` functions. The log level is automatically loaded from shared preferences at application startup via `TermuxApplication` and set in the static `Logger.CURRENT_LOG_LEVEL` variable. Changing the log level through the settings activity also changes the log level immediately. The 4 supported log levels are: - LOG_LEVEL_OFF which will log nothing. - LOG_LEVEL_NORMAL which will start logging error, warn and info messages and stacktraces. - LOG_LEVEL_DEBUG which will start logging debug messages. - LOG_LEVEL_VERBOSE which will start logging verbose messages. The default log level is `LOG_LEVEL_NORMAL` which will not log debug or verbose messages. Contributors can add useful log entries at those levels where ever they feel is appropriate so that it allows users and devs to more easily help solve issues or find bugs, specially without having to recompile termux after having to manually add general log entries to the source. DO NOT log data that may have private info of users like command arguments at log levels below debug, like `BackgroundJob` was doing previously. Logging to file support may be added later, will require log file rotation support and storage permissions.
2021-03-13 11:49:29 +00:00
Logger.logError(LOG_TAG, "Map passed to SharedProperties.putToProperties() is null");
2021-03-11 13:22:11 +00:00
return false;
}
// null keys are not allowed to be stored in mMap
if (key == null) {
Implement GUI based Termux settings manager and a centralized logging framework The settings activity can be accessed by long pressing on terminal view and selecting "Settings" from the popup shown. It uses the Android's Preference framework. Currently only debugging preferences to set log level and enabling terminal view key logging are provided. The Preference framework by default uses the keys set in `app:key` attribute in the respective preferences XML file to store the values in the default `SharedPreferences` file of the app. However, since we rely on `TermuxPreferenceConstants` and `TermuxPropertyConstants` classes to define key names so that they can be easily shared between termux and its plugin apps, we provide our own `PreferenceDataStore` for storing key/value pairs. The key name in the XML file can optionally be the same. Check `DebuggingPreferencesFragment` class for a sample. Each new preference category fragment should be added to `app/settings/` with its data store. This commit may allow support to be added for modifying `termux.properties` file directly from the UI but that requires more work, since writing to property files with comments require in-place modification. The `Logger` class provides various static functions for logging that should be used from now on instead of directly calling android `Log.*` functions. The log level is automatically loaded from shared preferences at application startup via `TermuxApplication` and set in the static `Logger.CURRENT_LOG_LEVEL` variable. Changing the log level through the settings activity also changes the log level immediately. The 4 supported log levels are: - LOG_LEVEL_OFF which will log nothing. - LOG_LEVEL_NORMAL which will start logging error, warn and info messages and stacktraces. - LOG_LEVEL_DEBUG which will start logging debug messages. - LOG_LEVEL_VERBOSE which will start logging verbose messages. The default log level is `LOG_LEVEL_NORMAL` which will not log debug or verbose messages. Contributors can add useful log entries at those levels where ever they feel is appropriate so that it allows users and devs to more easily help solve issues or find bugs, specially without having to recompile termux after having to manually add general log entries to the source. DO NOT log data that may have private info of users like command arguments at log levels below debug, like `BackgroundJob` was doing previously. Logging to file support may be added later, will require log file rotation support and storage permissions.
2021-03-13 11:49:29 +00:00
Logger.logError(LOG_TAG, "Cannot put a null key into properties map");
2021-03-11 13:22:11 +00:00
return false;
}
boolean put = false;
if (value != null) {
Class<?> clazz = value.getClass();
if (clazz.isPrimitive() || Primitives.isWrapperType(clazz) || value instanceof String) {
put = true;
}
} else {
put = true;
}
if (put) {
map.put(key, value);
return true;
} else {
Implement GUI based Termux settings manager and a centralized logging framework The settings activity can be accessed by long pressing on terminal view and selecting "Settings" from the popup shown. It uses the Android's Preference framework. Currently only debugging preferences to set log level and enabling terminal view key logging are provided. The Preference framework by default uses the keys set in `app:key` attribute in the respective preferences XML file to store the values in the default `SharedPreferences` file of the app. However, since we rely on `TermuxPreferenceConstants` and `TermuxPropertyConstants` classes to define key names so that they can be easily shared between termux and its plugin apps, we provide our own `PreferenceDataStore` for storing key/value pairs. The key name in the XML file can optionally be the same. Check `DebuggingPreferencesFragment` class for a sample. Each new preference category fragment should be added to `app/settings/` with its data store. This commit may allow support to be added for modifying `termux.properties` file directly from the UI but that requires more work, since writing to property files with comments require in-place modification. The `Logger` class provides various static functions for logging that should be used from now on instead of directly calling android `Log.*` functions. The log level is automatically loaded from shared preferences at application startup via `TermuxApplication` and set in the static `Logger.CURRENT_LOG_LEVEL` variable. Changing the log level through the settings activity also changes the log level immediately. The 4 supported log levels are: - LOG_LEVEL_OFF which will log nothing. - LOG_LEVEL_NORMAL which will start logging error, warn and info messages and stacktraces. - LOG_LEVEL_DEBUG which will start logging debug messages. - LOG_LEVEL_VERBOSE which will start logging verbose messages. The default log level is `LOG_LEVEL_NORMAL` which will not log debug or verbose messages. Contributors can add useful log entries at those levels where ever they feel is appropriate so that it allows users and devs to more easily help solve issues or find bugs, specially without having to recompile termux after having to manually add general log entries to the source. DO NOT log data that may have private info of users like command arguments at log levels below debug, like `BackgroundJob` was doing previously. Logging to file support may be added later, will require log file rotation support and storage permissions.
2021-03-13 11:49:29 +00:00
Logger.logError(LOG_TAG, "Cannot put a non-primitive value for the key \"" + key + "\" into properties map");
2021-03-11 13:22:11 +00:00
return false;
}
}
/**
* Put a value in a {@link Map}.
* The key cannot be {@code null}.
* Passing {@code null} as the value argument is equivalent to removing the key from the
* properties.
*
* @param properties The {@link Properties} object to add value to.
* @param key The key for which to add the value to the properties.
* @param value The {@link String} to add to the properties.
* @return Returns {@code true} if value was successfully added, otherwise {@code false}.
*/
public static boolean putToProperties(Properties properties, String key, String value) {
if (properties == null) {
Implement GUI based Termux settings manager and a centralized logging framework The settings activity can be accessed by long pressing on terminal view and selecting "Settings" from the popup shown. It uses the Android's Preference framework. Currently only debugging preferences to set log level and enabling terminal view key logging are provided. The Preference framework by default uses the keys set in `app:key` attribute in the respective preferences XML file to store the values in the default `SharedPreferences` file of the app. However, since we rely on `TermuxPreferenceConstants` and `TermuxPropertyConstants` classes to define key names so that they can be easily shared between termux and its plugin apps, we provide our own `PreferenceDataStore` for storing key/value pairs. The key name in the XML file can optionally be the same. Check `DebuggingPreferencesFragment` class for a sample. Each new preference category fragment should be added to `app/settings/` with its data store. This commit may allow support to be added for modifying `termux.properties` file directly from the UI but that requires more work, since writing to property files with comments require in-place modification. The `Logger` class provides various static functions for logging that should be used from now on instead of directly calling android `Log.*` functions. The log level is automatically loaded from shared preferences at application startup via `TermuxApplication` and set in the static `Logger.CURRENT_LOG_LEVEL` variable. Changing the log level through the settings activity also changes the log level immediately. The 4 supported log levels are: - LOG_LEVEL_OFF which will log nothing. - LOG_LEVEL_NORMAL which will start logging error, warn and info messages and stacktraces. - LOG_LEVEL_DEBUG which will start logging debug messages. - LOG_LEVEL_VERBOSE which will start logging verbose messages. The default log level is `LOG_LEVEL_NORMAL` which will not log debug or verbose messages. Contributors can add useful log entries at those levels where ever they feel is appropriate so that it allows users and devs to more easily help solve issues or find bugs, specially without having to recompile termux after having to manually add general log entries to the source. DO NOT log data that may have private info of users like command arguments at log levels below debug, like `BackgroundJob` was doing previously. Logging to file support may be added later, will require log file rotation support and storage permissions.
2021-03-13 11:49:29 +00:00
Logger.logError(LOG_TAG, "Properties passed to SharedProperties.putToProperties() is null");
2021-03-11 13:22:11 +00:00
return false;
}
// null keys are not allowed to be stored in mMap
if (key == null) {
Implement GUI based Termux settings manager and a centralized logging framework The settings activity can be accessed by long pressing on terminal view and selecting "Settings" from the popup shown. It uses the Android's Preference framework. Currently only debugging preferences to set log level and enabling terminal view key logging are provided. The Preference framework by default uses the keys set in `app:key` attribute in the respective preferences XML file to store the values in the default `SharedPreferences` file of the app. However, since we rely on `TermuxPreferenceConstants` and `TermuxPropertyConstants` classes to define key names so that they can be easily shared between termux and its plugin apps, we provide our own `PreferenceDataStore` for storing key/value pairs. The key name in the XML file can optionally be the same. Check `DebuggingPreferencesFragment` class for a sample. Each new preference category fragment should be added to `app/settings/` with its data store. This commit may allow support to be added for modifying `termux.properties` file directly from the UI but that requires more work, since writing to property files with comments require in-place modification. The `Logger` class provides various static functions for logging that should be used from now on instead of directly calling android `Log.*` functions. The log level is automatically loaded from shared preferences at application startup via `TermuxApplication` and set in the static `Logger.CURRENT_LOG_LEVEL` variable. Changing the log level through the settings activity also changes the log level immediately. The 4 supported log levels are: - LOG_LEVEL_OFF which will log nothing. - LOG_LEVEL_NORMAL which will start logging error, warn and info messages and stacktraces. - LOG_LEVEL_DEBUG which will start logging debug messages. - LOG_LEVEL_VERBOSE which will start logging verbose messages. The default log level is `LOG_LEVEL_NORMAL` which will not log debug or verbose messages. Contributors can add useful log entries at those levels where ever they feel is appropriate so that it allows users and devs to more easily help solve issues or find bugs, specially without having to recompile termux after having to manually add general log entries to the source. DO NOT log data that may have private info of users like command arguments at log levels below debug, like `BackgroundJob` was doing previously. Logging to file support may be added later, will require log file rotation support and storage permissions.
2021-03-13 11:49:29 +00:00
Logger.logError(LOG_TAG, "Cannot put a null key into properties");
2021-03-11 13:22:11 +00:00
return false;
}
if (value != null) {
properties.put(key, value);
return true;
} else {
properties.remove(key);
}
return true;
}
public static Properties getPropertiesCopy(Properties inputProperties) {
2021-04-06 12:17:12 +00:00
if (inputProperties == null) return null;
2021-03-11 13:22:11 +00:00
Properties outputProperties = new Properties();
for (String key : inputProperties.stringPropertyNames()) {
outputProperties.put(key, inputProperties.get(key));
2021-03-11 13:22:11 +00:00
}
return outputProperties;
}
2021-03-11 13:22:11 +00:00
public static Map<String, Object> getMapCopy(Map<String, Object> map) {
2021-04-06 12:17:12 +00:00
if (map == null) return null;
return new HashMap<>(map);
2021-03-11 13:22:11 +00:00
}
2021-03-11 13:22:11 +00:00
/**
* Get the boolean value for the {@link String} value.
2021-03-11 13:22:11 +00:00
*
* @param value The {@link String} value to convert.
* @param def The default {@link boolean} value to return.
* @return Returns {@code true} or {@code false} if value is the literal string "true" or "false" respectively,
* regardless of case. Otherwise returns default value.
2021-03-11 13:22:11 +00:00
*/
public static boolean getBooleanValueForStringValue(String value, boolean def) {
return (boolean) getDefaultIfNull(MAP_GENERIC_BOOLEAN.get(toLowerCase(value)), def);
2021-03-11 13:22:11 +00:00
}
/**
* Get the inverted boolean value for the {@link String} value.
2021-03-11 13:22:11 +00:00
*
* @param value The {@link String} value to convert.
* @param def The default {@link boolean} value to return.
* @return Returns {@code true} or {@code false} if value is the literal string "false" or "true" respectively,
* regardless of case. Otherwise returns default value.
2021-03-11 13:22:11 +00:00
*/
public static boolean getInvertedBooleanValueForStringValue(String value, boolean def) {
return (boolean) getDefaultIfNull(MAP_GENERIC_INVERTED_BOOLEAN.get(toLowerCase(value)), def);
2021-03-11 13:22:11 +00:00
}
/**
* Get the object itself if it is not {@code null}, otherwise default.
2021-03-11 13:22:11 +00:00
*
* @param object The {@link Object} to check.
* @param def The default {@link Object}.
* @return Returns {@code object} if it is not {@code null}, otherwise returns {@code def}.
2021-03-11 13:22:11 +00:00
*/
public static <T> T getDefaultIfNull(@androidx.annotation.Nullable T object, @androidx.annotation.Nullable T def) {
return (object == null) ? def : object;
2021-03-11 13:22:11 +00:00
}
/**
* Covert the {@link String} value to lowercase.
2021-03-11 13:22:11 +00:00
*
* @param value The {@link String} value to convert.
* @return Returns the lowercased value.
2021-03-11 13:22:11 +00:00
*/
public static String toLowerCase(String value) {
if (value == null) return null; else return value.toLowerCase();
2021-03-11 13:22:11 +00:00
}
}