/* * Copyright (C) 2013 The Android Open Source Project * modified * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only */ package helium314.keyboard.latin.settings; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.TypedValue; import android.view.ContextThemeWrapper; import android.view.Gravity; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; import helium314.keyboard.keyboard.KeyboardActionListener; import helium314.keyboard.keyboard.KeyboardTheme; import helium314.keyboard.keyboard.internal.keyboard_parser.LocaleKeyboardInfosKt; import helium314.keyboard.latin.AudioAndHapticFeedbackManager; import helium314.keyboard.latin.InputAttributes; import helium314.keyboard.latin.R; import helium314.keyboard.latin.common.Colors; import helium314.keyboard.latin.common.LocaleUtils; import helium314.keyboard.latin.utils.AdditionalSubtypeUtils; import helium314.keyboard.latin.utils.ColorUtilKt; import helium314.keyboard.latin.utils.DeviceProtectedUtils; import helium314.keyboard.latin.utils.JniUtils; import helium314.keyboard.latin.utils.Log; import helium314.keyboard.latin.utils.ResourceUtils; import helium314.keyboard.latin.utils.RunInLocaleKt; import helium314.keyboard.latin.utils.StatsUtils; import helium314.keyboard.latin.utils.SubtypeSettingsKt; import helium314.keyboard.latin.utils.ToolbarKey; import helium314.keyboard.latin.utils.ToolbarUtilsKt; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = Settings.class.getSimpleName(); // Settings screens public static final String SCREEN_DEBUG = "screen_debug"; public static final String SCREEN_GESTURE = "screen_gesture"; // theme-related stuff public static final String PREF_THEME_STYLE = "theme_style"; public static final String PREF_ICON_STYLE = "icon_style"; public static final String PREF_THEME_COLORS = "theme_colors"; public static final String PREF_THEME_COLORS_NIGHT = "theme_colors_night"; public static final String PREF_THEME_KEY_BORDERS = "theme_key_borders"; public static final String PREF_THEME_DAY_NIGHT = "theme_auto_day_night"; public static final String PREF_THEME_USER_COLOR_PREFIX = "theme_color_"; public static final String PREF_THEME_USER_COLOR_NIGHT_PREFIX = "theme_dark_color_"; public static final String PREF_COLOR_KEYS_SUFFIX = "keys"; public static final String PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX = "functional_keys"; public static final String PREF_COLOR_SPACEBAR_SUFFIX = "spacebar"; public static final String PREF_COLOR_SPACEBAR_TEXT_SUFFIX = "spacebar_text"; public static final String PREF_COLOR_ACCENT_SUFFIX = "accent"; public static final String PREF_COLOR_GESTURE_SUFFIX = "gesture"; public static final String PREF_COLOR_TEXT_SUFFIX = "text"; public static final String PREF_COLOR_SUGGESTION_TEXT_SUFFIX = "suggestion_text"; public static final String PREF_COLOR_HINT_TEXT_SUFFIX = "hint_text"; public static final String PREF_COLOR_BACKGROUND_SUFFIX = "background"; public static final String PREF_AUTO_USER_COLOR_SUFFIX = "_auto"; public static final String PREF_ALL_COLORS_SUFFIX = "all_colors"; public static final String PREF_CUSTOM_ICON_NAMES = "custom_icon_names"; public static final String PREF_TOOLBAR_CUSTOM_KEY_CODES = "toolbar_custom_key_codes"; public static final String PREF_TOOLBAR_CUSTOM_LONGPRESS_CODES = "toolbar_custom_longpress_codes"; public static final String PREF_AUTO_CAP = "auto_cap"; public static final String PREF_VIBRATE_ON = "vibrate_on"; public static final String PREF_VIBRATE_IN_DND_MODE = "vibrate_in_dnd_mode"; public static final String PREF_SOUND_ON = "sound_on"; public static final String PREF_POPUP_ON = "popup_on"; public static final String PREF_AUTO_CORRECTION = "auto_correction"; public static final String PREF_MORE_AUTO_CORRECTION = "more_auto_correction"; public static final String PREF_AUTO_CORRECTION_CONFIDENCE = "auto_correction_confidence"; public static final String PREF_AUTOCORRECT_SHORTCUTS = "autocorrect_shortcuts"; public static final String PREF_CENTER_SUGGESTION_TEXT_TO_ENTER = "center_suggestion_text_to_enter"; public static final String PREF_SHOW_SUGGESTIONS = "show_suggestions"; public static final String PREF_ALWAYS_SHOW_SUGGESTIONS = "always_show_suggestions"; public static final String PREF_KEY_USE_PERSONALIZED_DICTS = "use_personalized_dicts"; public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD = "use_double_space_period"; public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE = "block_potentially_offensive"; public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY = "show_language_switch_key"; public static final String PREF_LANGUAGE_SWITCH_KEY = "language_switch_key"; public static final String PREF_SHOW_EMOJI_KEY = "show_emoji_key"; public static final String PREF_VARIABLE_TOOLBAR_DIRECTION = "var_toolbar_direction"; public static final String PREF_ADDITIONAL_SUBTYPES = "additional_subtypes"; public static final String PREF_ENABLE_SPLIT_KEYBOARD = "split_keyboard"; public static final String PREF_SPLIT_SPACER_SCALE = "split_spacer_scale"; public static final String PREF_KEYBOARD_HEIGHT_SCALE = "keyboard_height_scale"; public static final String PREF_BOTTOM_PADDING_SCALE = "bottom_padding_scale"; public static final String PREF_BOTTOM_PADDING_SCALE_LANDSCAPE = "bottom_padding_scale_landscape"; public static final String PREF_SIDE_PADDING_SCALE = "side_padding_scale"; public static final String PREF_SIDE_PADDING_SCALE_LANDSCAPE = "side_padding_scale_landscape"; public static final String PREF_FONT_SCALE = "font_scale"; public static final String PREF_EMOJI_FONT_SCALE = "emoji_font_scale"; public static final String PREF_SPACE_HORIZONTAL_SWIPE = "horizontal_space_swipe"; public static final String PREF_SPACE_VERTICAL_SWIPE = "vertical_space_swipe"; public static final String PREF_DELETE_SWIPE = "delete_swipe"; public static final String PREF_AUTOSPACE_AFTER_PUNCTUATION = "autospace_after_punctuation"; public static final String PREF_ALWAYS_INCOGNITO_MODE = "always_incognito_mode"; public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction"; public static final String PREF_SUGGEST_CLIPBOARD_CONTENT = "suggest_clipboard_content"; public static final String PREF_GESTURE_INPUT = "gesture_input"; public static final String PREF_VIBRATION_DURATION_SETTINGS = "vibration_duration_settings"; public static final String PREF_KEYPRESS_SOUND_VOLUME = "keypress_sound_volume"; public static final String PREF_KEY_LONGPRESS_TIMEOUT = "key_longpress_timeout"; public static final String PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY = "enable_emoji_alt_physical_key"; public static final String PREF_GESTURE_PREVIEW_TRAIL = "gesture_preview_trail"; public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT = "gesture_floating_preview_text"; public static final String PREF_GESTURE_FLOATING_PREVIEW_DYNAMIC = "gesture_floating_preview_dynamic"; public static final String PREF_GESTURE_DYNAMIC_PREVIEW_FOLLOW_SYSTEM = "gesture_dynamic_preview_follow_system"; public static final String PREF_GESTURE_SPACE_AWARE = "gesture_space_aware"; public static final String PREF_GESTURE_FAST_TYPING_COOLDOWN = "gesture_fast_typing_cooldown"; public static final String PREF_GESTURE_TRAIL_FADEOUT_DURATION = "gesture_trail_fadeout_duration"; public static final String PREF_SHOW_SETUP_WIZARD_ICON = "show_setup_wizard_icon"; public static final String PREF_USE_CONTACTS = "use_contacts"; public static final String PREFS_LONG_PRESS_SYMBOLS_FOR_NUMPAD = "long_press_symbols_for_numpad"; // one-handed mode gravity, enablement and scale, stored separately per orientation public static final String PREF_ONE_HANDED_MODE_PREFIX = "one_handed_mode_enabled_p_"; public static final String PREF_ONE_HANDED_GRAVITY_PREFIX = "one_handed_mode_gravity_p_"; public static final String PREF_ONE_HANDED_SCALE_PREFIX = "one_handed_mode_scale_p_"; public static final String PREF_SHOW_NUMBER_ROW = "show_number_row"; public static final String PREF_LOCALIZED_NUMBER_ROW = "localized_number_row"; public static final String PREF_SHOW_NUMBER_ROW_HINTS = "show_number_row_hints"; public static final String PREF_CUSTOM_CURRENCY_KEY = "custom_currency_key"; public static final String PREF_SHOW_HINTS = "show_hints"; public static final String PREF_POPUP_KEYS_ORDER = "popup_keys_order"; public static final String PREF_POPUP_KEYS_LABELS_ORDER = "popup_keys_labels_order"; public static final String PREF_SHOW_POPUP_HINTS = "show_popup_hints"; public static final String PREF_MORE_POPUP_KEYS = "more_popup_keys"; public static final String PREF_SPACE_TO_CHANGE_LANG = "prefs_long_press_keyboard_to_change_lang"; public static final String PREF_ENABLE_CLIPBOARD_HISTORY = "enable_clipboard_history"; public static final String PREF_CLIPBOARD_HISTORY_RETENTION_TIME = "clipboard_history_retention_time"; public static final String PREF_SECONDARY_LOCALES_PREFIX = "secondary_locales_"; public static final String PREF_ADD_TO_PERSONAL_DICTIONARY = "add_to_personal_dictionary"; public static final String PREF_NAVBAR_COLOR = "navbar_color"; public static final String PREF_NARROW_KEY_GAPS = "narrow_key_gaps"; public static final String PREF_ENABLED_SUBTYPES = "enabled_subtypes"; public static final String PREF_SELECTED_SUBTYPE = "selected_subtype"; public static final String PREF_USE_SYSTEM_LOCALES = "use_system_locales"; public static final String PREF_URL_DETECTION = "url_detection"; public static final String PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG = "dont_show_missing_dict_dialog"; public static final String PREF_QUICK_PIN_TOOLBAR_KEYS = "quick_pin_toolbar_keys"; public static final String PREF_PINNED_TOOLBAR_KEYS = "pinned_toolbar_keys"; public static final String PREF_TOOLBAR_KEYS = "toolbar_keys"; public static final String PREF_AUTO_SHOW_TOOLBAR = "auto_show_toolbar"; public static final String PREF_AUTO_HIDE_TOOLBAR = "auto_hide_toolbar"; public static final String PREF_CLIPBOARD_TOOLBAR_KEYS = "clipboard_toolbar_keys"; public static final String PREF_ABC_AFTER_EMOJI = "abc_after_emoji"; public static final String PREF_ABC_AFTER_CLIP = "abc_after_clip"; public static final String PREF_ABC_AFTER_SYMBOL_SPACE = "abc_after_symbol_space"; public static final String PREF_REMOVE_REDUNDANT_POPUPS = "remove_redundant_popups"; public static final String PREF_SPACE_BAR_TEXT = "space_bar_text"; // Emoji public static final String PREF_EMOJI_MAX_SDK = "emoji_max_sdk"; public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys"; public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_ID = "last_shown_emoji_category_id"; public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID = "last_shown_emoji_category_page_id"; public static final String PREF_PINNED_CLIPS = "pinned_clips"; public static final String PREF_VERSION_CODE = "version_code"; public static final String PREF_SHOW_MORE_COLORS = "show_more_colors"; public static final String PREF_LIBRARY_CHECKSUM = "lib_checksum"; private static final float UNDEFINED_PREFERENCE_VALUE_FLOAT = -1.0f; private static final int UNDEFINED_PREFERENCE_VALUE_INT = -1; private Context mContext; private SharedPreferences mPrefs; private SettingsValues mSettingsValues; private final ReentrantLock mSettingsValuesLock = new ReentrantLock(); // static cache for background images to avoid potentially slow reload on every settings reload private final static Drawable[] sCachedBackgroundImages = new Drawable[4]; private static Typeface sCachedTypeface; private static boolean sCustomTypefaceLoaded; // to avoid repeatedly checking custom typeface file when there is no custom typeface private Map mCustomToolbarKeyCodes = null; private Map mCustomToolbarLongpressCodes = null; private static final Settings sInstance = new Settings(); // preferences that are not used in SettingsValues and thus should not trigger reload when changed private static final HashSet dontReloadOnChanged = new HashSet<>() {{ add(PREF_PINNED_CLIPS); add(PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID); add(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID); add(PREF_EMOJI_RECENT_KEYS); add(PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG); add(PREF_SELECTED_SUBTYPE); }}; public static Settings getInstance() { return sInstance; } public static void init(final Context context) { sInstance.onCreate(context); } private Settings() { // Intentional empty constructor for singleton. } private void onCreate(final Context context) { mContext = context; mPrefs = DeviceProtectedUtils.getSharedPreferences(context); mPrefs.registerOnSharedPreferenceChangeListener(this); } public void onDestroy() { mPrefs.unregisterOnSharedPreferenceChangeListener(this); } @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { if (dontReloadOnChanged.contains(key)) return; mSettingsValuesLock.lock(); try { if (mSettingsValues == null) { // TODO: Introduce a static function to register this class and ensure that // loadSettings must be called before "onSharedPreferenceChanged" is called. Log.w(TAG, "onSharedPreferenceChanged called before loadSettings."); return; } mCustomToolbarLongpressCodes = null; mCustomToolbarKeyCodes = null; loadSettings(mContext, mSettingsValues.mLocale, mSettingsValues.mInputAttributes); StatsUtils.onLoadSettings(mSettingsValues); } finally { mSettingsValuesLock.unlock(); } if (PREF_ADDITIONAL_SUBTYPES.equals(key)) { final String additionalSubtypes = readPrefAdditionalSubtypes(prefs, mContext.getResources()); SubtypeSettingsKt.updateAdditionalSubtypes(AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypes)); } } public void loadSettings(final Context context, final Locale locale, @NonNull final InputAttributes inputAttributes) { mSettingsValuesLock.lock(); mContext = context; try { final SharedPreferences prefs = mPrefs; Log.i(TAG, "loadSettings"); mSettingsValues = RunInLocaleKt.runInLocale(context, locale, ctx -> new SettingsValues(ctx, prefs, ctx.getResources(), inputAttributes)); } finally { mSettingsValuesLock.unlock(); } } public void stopListener() { mPrefs.unregisterOnSharedPreferenceChangeListener(this); } public void startListener() { mPrefs.registerOnSharedPreferenceChangeListener(this); } // TODO: Remove this method and add proxy method to SettingsValues. public SettingsValues getCurrent() { return mSettingsValues; } public static int readScreenMetrics(final Resources res) { return res.getInteger(R.integer.config_screen_metrics); } // Accessed from the settings interface, hence public public static boolean readKeypressSoundEnabled(final SharedPreferences prefs, final Resources res) { return prefs.getBoolean(PREF_SOUND_ON, res.getBoolean(R.bool.config_default_sound_enabled)); } public static boolean readVibrationEnabled(final SharedPreferences prefs, final Resources res) { return prefs.getBoolean(PREF_VIBRATE_ON, res.getBoolean(R.bool.config_default_vibration_enabled)) && AudioAndHapticFeedbackManager.getInstance().hasVibrator(); } public static boolean readAutoCorrectEnabled(final SharedPreferences prefs) { return prefs.getBoolean(PREF_AUTO_CORRECTION, true); } public static boolean readMoreAutoCorrectEnabled(final SharedPreferences prefs) { return prefs.getBoolean(PREF_MORE_AUTO_CORRECTION, true); } public void toggleAutoCorrect() { mPrefs.edit().putBoolean(Settings.PREF_AUTO_CORRECTION, !readAutoCorrectEnabled(mPrefs)).apply(); } public static String readAutoCorrectConfidence(final SharedPreferences prefs, final Resources res) { return prefs.getString(PREF_AUTO_CORRECTION_CONFIDENCE, res.getString(R.string.auto_correction_threshold_mode_index_modest)); } public static boolean readCenterSuggestionTextToEnter(final SharedPreferences prefs, final Resources res) { return prefs.getBoolean(PREF_CENTER_SUGGESTION_TEXT_TO_ENTER, res.getBoolean(R.bool.config_center_suggestion_text_to_enter)); } public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs, final Resources res) { return prefs.getBoolean(PREF_BLOCK_POTENTIALLY_OFFENSIVE, res.getBoolean(R.bool.config_block_potentially_offensive)); } public static boolean readGestureInputEnabled(final SharedPreferences prefs) { return JniUtils.sHaveGestureLib && prefs.getBoolean(PREF_GESTURE_INPUT, true); } public static boolean readGestureDynamicPreviewEnabled(final SharedPreferences prefs, final Context context) { final boolean followSystem = prefs.getBoolean(PREF_GESTURE_DYNAMIC_PREVIEW_FOLLOW_SYSTEM, true); final boolean defValue = readGestureDynamicPreviewDefault(context); final boolean curValue = prefs.getBoolean(Settings.PREF_GESTURE_FLOATING_PREVIEW_DYNAMIC, defValue); return followSystem ? defValue : curValue; } public static boolean readGestureDynamicPreviewDefault(final Context context) { // if transitions are disabled for the system (reduced motion), moving preview should be disabled return android.provider.Settings.System.getFloat( context.getContentResolver(), android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1.0f ) != 0.0f; } public static int readGestureFastTypingCooldown(final SharedPreferences prefs, final Resources res) { final int milliseconds = prefs.getInt( PREF_GESTURE_FAST_TYPING_COOLDOWN, UNDEFINED_PREFERENCE_VALUE_INT); return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds : readDefaultGestureFastTypingCooldown(res); } public static int readDefaultGestureFastTypingCooldown(final Resources res) { return res.getInteger(R.integer.config_gesture_static_time_threshold_after_fast_typing); } public static int readGestureTrailFadeoutDuration(final SharedPreferences prefs, final Resources res) { final int milliseconds = prefs.getInt( PREF_GESTURE_TRAIL_FADEOUT_DURATION, UNDEFINED_PREFERENCE_VALUE_INT); return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds : readDefaultGestureTrailFadeoutDuration(res); } public static int readDefaultGestureTrailFadeoutDuration(final Resources res) { return res.getInteger(R.integer.config_gesture_trail_fadeout_duration_default); } public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs, final Resources res) { final boolean defaultKeyPreviewPopup = res.getBoolean(R.bool.config_default_key_preview_popup); return prefs.getBoolean(PREF_POPUP_ON, defaultKeyPreviewPopup); } public static boolean readAlwaysIncognitoMode(final SharedPreferences prefs) { return prefs.getBoolean(PREF_ALWAYS_INCOGNITO_MODE, false); } public void toggleAlwaysIncognitoMode() { mPrefs.edit().putBoolean(Settings.PREF_ALWAYS_INCOGNITO_MODE, !readAlwaysIncognitoMode(mPrefs)).apply(); } public static String readPrefAdditionalSubtypes(final SharedPreferences prefs, final Resources res) { final String predefinedPrefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes( res.getStringArray(R.array.predefined_subtypes)); return prefs.getString(PREF_ADDITIONAL_SUBTYPES, predefinedPrefSubtypes); } public static void writePrefAdditionalSubtypes(final SharedPreferences prefs, final String prefSubtypes) { prefs.edit().putString(PREF_ADDITIONAL_SUBTYPES, prefSubtypes).apply(); } public static float readKeypressSoundVolume(final SharedPreferences prefs) { return prefs.getFloat(PREF_KEYPRESS_SOUND_VOLUME, UNDEFINED_PREFERENCE_VALUE_FLOAT); } public static int readKeyLongpressTimeout(final SharedPreferences prefs, final Resources res) { final int milliseconds = prefs.getInt( PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT); return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds : readDefaultKeyLongpressTimeout(res); } public static int readDefaultKeyLongpressTimeout(final Resources res) { return res.getInteger(R.integer.config_default_longpress_key_timeout); } public static int readKeypressVibrationDuration(final SharedPreferences prefs) { return prefs.getInt(PREF_VIBRATION_DURATION_SETTINGS, UNDEFINED_PREFERENCE_VALUE_INT); } public static boolean readClipboardHistoryEnabled(final SharedPreferences prefs) { return prefs.getBoolean(PREF_ENABLE_CLIPBOARD_HISTORY, true); } public static int readClipboardHistoryRetentionTime(final SharedPreferences prefs, final Resources res) { final int minutes = prefs.getInt( PREF_CLIPBOARD_HISTORY_RETENTION_TIME, UNDEFINED_PREFERENCE_VALUE_INT); return (minutes != UNDEFINED_PREFERENCE_VALUE_INT) ? minutes : readDefaultClipboardHistoryRetentionTime(res); } public static int readDefaultClipboardHistoryRetentionTime(final Resources res) { return res.getInteger(R.integer.config_clipboard_history_retention_time); } public static int readHorizontalSpaceSwipe(final SharedPreferences prefs) { return switch (prefs.getString(PREF_SPACE_HORIZONTAL_SWIPE, "none")) { case "move_cursor" -> KeyboardActionListener.SWIPE_MOVE_CURSOR; case "switch_language" -> KeyboardActionListener.SWIPE_SWITCH_LANGUAGE; case "toggle_numpad" -> KeyboardActionListener.SWIPE_TOGGLE_NUMPAD; default -> KeyboardActionListener.SWIPE_NO_ACTION; }; } public static int readVerticalSpaceSwipe(final SharedPreferences prefs) { return switch (prefs.getString(PREF_SPACE_VERTICAL_SWIPE, "none")) { case "move_cursor" -> KeyboardActionListener.SWIPE_MOVE_CURSOR; case "switch_language" -> KeyboardActionListener.SWIPE_SWITCH_LANGUAGE; case "toggle_numpad" -> KeyboardActionListener.SWIPE_TOGGLE_NUMPAD; default -> KeyboardActionListener.SWIPE_NO_ACTION; }; } public static boolean readDeleteSwipeEnabled(final SharedPreferences prefs) { return prefs.getBoolean(PREF_DELETE_SWIPE, true); } public static boolean readAutospaceAfterPunctuationEnabled(final SharedPreferences prefs) { return prefs.getBoolean(PREF_AUTOSPACE_AFTER_PUNCTUATION, false); } public static boolean readFullscreenModeAllowed(final Resources res) { return res.getBoolean(R.bool.config_fullscreen_mode_allowed); } public static boolean readShowSetupWizardIcon(final SharedPreferences prefs, final Context context) { if (!prefs.contains(PREF_SHOW_SETUP_WIZARD_ICON)) { final ApplicationInfo appInfo = context.getApplicationInfo(); final boolean isApplicationInSystemImage = (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; // Default value return !isApplicationInSystemImage; } return prefs.getBoolean(PREF_SHOW_SETUP_WIZARD_ICON, false); } public static boolean readOneHandedModeEnabled(final SharedPreferences prefs, final boolean portrait) { return prefs.getBoolean(PREF_ONE_HANDED_MODE_PREFIX + portrait, false); } public void writeOneHandedModeEnabled(final boolean enabled) { mPrefs.edit().putBoolean(PREF_ONE_HANDED_MODE_PREFIX + (getCurrent().mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT), enabled).apply(); } public static float readOneHandedModeScale(final SharedPreferences prefs, final boolean portrait) { return prefs.getFloat(PREF_ONE_HANDED_SCALE_PREFIX + portrait, 1f); } public void writeOneHandedModeScale(final Float scale) { mPrefs.edit().putFloat(PREF_ONE_HANDED_SCALE_PREFIX + (getCurrent().mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT), scale).apply(); } @SuppressLint("RtlHardcoded") public static int readOneHandedModeGravity(final SharedPreferences prefs, final boolean portrait) { return prefs.getInt(PREF_ONE_HANDED_GRAVITY_PREFIX + portrait, Gravity.LEFT); } public void writeOneHandedModeGravity(final int gravity) { mPrefs.edit().putInt(PREF_ONE_HANDED_GRAVITY_PREFIX + (getCurrent().mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT), gravity).apply(); } public static float readBottomPaddingScale(final SharedPreferences prefs, final boolean landscape) { if (landscape) return prefs.getFloat(PREF_BOTTOM_PADDING_SCALE_LANDSCAPE, 0f); return prefs.getFloat(PREF_BOTTOM_PADDING_SCALE, SettingsValues.DEFAULT_SIZE_SCALE); } public static float readSidePaddingScale(final SharedPreferences prefs, final boolean landscape) { if (landscape) return prefs.getFloat(PREF_SIDE_PADDING_SCALE_LANDSCAPE, 0f); return prefs.getFloat(PREF_SIDE_PADDING_SCALE, 0f); } public static boolean readHasHardwareKeyboard(final Configuration conf) { // The standard way of finding out whether we have a hardware keyboard. This code is taken // from InputMethodService#onEvaluateInputShown, which canonically determines this. // In a nutshell, we have a keyboard if the configuration says the type of hardware keyboard // is NOKEYS and if it's not hidden (e.g. folded inside the device). return conf.keyboard != Configuration.KEYBOARD_NOKEYS && conf.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES; } public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) { prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply(); } public static String readEmojiRecentKeys(final SharedPreferences prefs) { return prefs.getString(PREF_EMOJI_RECENT_KEYS, ""); } public static void writeLastShownEmojiCategoryId( final SharedPreferences prefs, final int categoryId) { prefs.edit().putInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, categoryId).apply(); } public static int readLastShownEmojiCategoryId( final SharedPreferences prefs, final int defValue) { return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, defValue); } public static void writeLastShownEmojiCategoryPageId( final SharedPreferences prefs, final int categoryId) { prefs.edit().putInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID, categoryId).apply(); } public static int readLastShownEmojiCategoryPageId( final SharedPreferences prefs, final int defValue) { return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID, defValue); } public static String readPinnedClipString(final Context context) { try { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); return prefs.getString(PREF_PINNED_CLIPS, ""); } catch (final IllegalStateException e) { // SharedPreferences in credential encrypted storage are not available until after user is unlocked return ""; } } public static void writePinnedClipString(final Context context, final String clips) { try { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs.edit().putString(PREF_PINNED_CLIPS, clips).apply(); } catch (final IllegalStateException e) { // SharedPreferences in credential encrypted storage are not available until after user is unlocked } } public static int readMorePopupKeysPref(final SharedPreferences prefs) { return switch (prefs.getString(Settings.PREF_MORE_POPUP_KEYS, "main")) { case "all" -> LocaleKeyboardInfosKt.POPUP_KEYS_ALL; case "more" -> LocaleKeyboardInfosKt.POPUP_KEYS_MORE; case "normal" -> LocaleKeyboardInfosKt.POPUP_KEYS_NORMAL; default -> LocaleKeyboardInfosKt.POPUP_KEYS_MAIN; }; } @Nullable public static Drawable readUserBackgroundImage(final Context context, final boolean night) { final boolean landscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; final int index = (night ? 1 : 0) + (landscape ? 2 : 0); if (sCachedBackgroundImages[index] != null) return sCachedBackgroundImages[index]; File image = getCustomBackgroundFile(context, night, landscape); if (!image.isFile() && landscape) image = getCustomBackgroundFile(context, night, false); // fall back to portrait image for historic reasons if (!image.isFile()) return null; try { sCachedBackgroundImages[index] = new BitmapDrawable(context.getResources(), BitmapFactory.decodeFile(image.getAbsolutePath())); return sCachedBackgroundImages[index]; } catch (Exception e) { return null; } } public static File getCustomBackgroundFile(final Context context, final boolean night, final boolean landscape) { return new File(DeviceProtectedUtils.getFilesDir(context), "custom_background_image" + (landscape ? "_landscape" : "") + (night ? "_night" : "")); } public static boolean readDayNightPref(final SharedPreferences prefs, final Resources res) { return prefs.getBoolean(PREF_THEME_DAY_NIGHT, res.getBoolean(R.bool.day_night_default)); } public static void clearCachedBackgroundImages() { Arrays.fill(sCachedBackgroundImages, null); } public static List getSecondaryLocales(final SharedPreferences prefs, final Locale mainLocale) { final String localesString = prefs.getString(PREF_SECONDARY_LOCALES_PREFIX + mainLocale.toLanguageTag(), ""); final ArrayList locales = new ArrayList<>(); for (String languageTag : localesString.split(";")) { if (languageTag.isEmpty()) continue; locales.add(LocaleUtils.constructLocale(languageTag)); } return locales; } public static void setSecondaryLocales(final SharedPreferences prefs, final Locale mainLocale, final List locales) { if (locales.isEmpty()) { prefs.edit().putString(PREF_SECONDARY_LOCALES_PREFIX + mainLocale.toLanguageTag(), "").apply(); return; } final StringBuilder sb = new StringBuilder(); for (Locale locale : locales) { sb.append(";").append(locale.toLanguageTag()); } prefs.edit().putString(PREF_SECONDARY_LOCALES_PREFIX + mainLocale.toLanguageTag(), sb.toString()).apply(); } public static Colors getColorsForCurrentTheme(final Context context, final SharedPreferences prefs) { boolean isNight = ResourceUtils.isNight(context.getResources()); if (ColorsSettingsFragment.Companion.getForceOppositeTheme()) isNight = !isNight; else isNight = isNight && readDayNightPref(prefs, context.getResources()); final String themeColors = (isNight) ? prefs.getString(Settings.PREF_THEME_COLORS_NIGHT, KeyboardTheme.THEME_DARK) : prefs.getString(Settings.PREF_THEME_COLORS, KeyboardTheme.THEME_LIGHT); final String themeStyle = prefs.getString(Settings.PREF_THEME_STYLE, KeyboardTheme.STYLE_MATERIAL); return KeyboardTheme.getThemeColors(themeColors, themeStyle, context, prefs, isNight); } public static int readUserColor(final SharedPreferences prefs, final Context context, final String colorName, final boolean isNight) { final String pref = getColorPref(colorName, isNight); if (prefs.getBoolean(pref + PREF_AUTO_USER_COLOR_SUFFIX, true)) { return determineAutoColor(prefs, context, colorName, isNight); } if (prefs.contains(pref)) return prefs.getInt(pref, Color.GRAY); else return determineAutoColor(prefs, context, colorName, isNight); } public static String getColorPref(final String color, final boolean isNight) { return (isNight ? PREF_THEME_USER_COLOR_NIGHT_PREFIX : PREF_THEME_USER_COLOR_PREFIX) + color; } private static int determineAutoColor(final SharedPreferences prefs, final Context context, final String color, final boolean isNight) { switch (color) { case PREF_COLOR_ACCENT_SUFFIX: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // try determining accent color on Android 10 & 11, accent is not available in resources final Context wrapper = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault); final TypedValue value = new TypedValue(); if (wrapper.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true)) return value.data; } return ContextCompat.getColor(getDayNightContext(context, isNight), R.color.accent); case PREF_COLOR_GESTURE_SUFFIX: return readUserColor(prefs, context, PREF_COLOR_ACCENT_SUFFIX, isNight); case PREF_COLOR_SUGGESTION_TEXT_SUFFIX: return readUserColor(prefs, context, PREF_COLOR_TEXT_SUFFIX, isNight); case PREF_COLOR_TEXT_SUFFIX: // base it on background color, and not key, because it's also used for suggestions final int background = readUserColor(prefs, context, PREF_COLOR_BACKGROUND_SUFFIX, isNight); if (ColorUtilKt.isBrightColor(background)) { // but if key borders are enabled, we still want reasonable contrast if (!prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, false) || ColorUtilKt.isGoodContrast(Color.BLACK, readUserColor(prefs, context, PREF_COLOR_KEYS_SUFFIX, isNight))) return Color.BLACK; else return Color.GRAY; } else return Color.WHITE; case PREF_COLOR_HINT_TEXT_SUFFIX: if (ColorUtilKt.isBrightColor(readUserColor(prefs, context, PREF_COLOR_KEYS_SUFFIX, isNight))) return Color.DKGRAY; else return readUserColor(prefs, context, PREF_COLOR_TEXT_SUFFIX, isNight); case PREF_COLOR_KEYS_SUFFIX: return ColorUtilKt.brightenOrDarken(readUserColor(prefs, context, PREF_COLOR_BACKGROUND_SUFFIX, isNight), isNight); case PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX: return ColorUtilKt.brightenOrDarken(readUserColor(prefs, context, PREF_COLOR_KEYS_SUFFIX, isNight), true); case PREF_COLOR_SPACEBAR_SUFFIX: return readUserColor(prefs, context, PREF_COLOR_KEYS_SUFFIX, isNight); case PREF_COLOR_SPACEBAR_TEXT_SUFFIX: final int spacebar = readUserColor(prefs, context, PREF_COLOR_SPACEBAR_SUFFIX, isNight); final int hintText = readUserColor(prefs, context, PREF_COLOR_HINT_TEXT_SUFFIX, isNight); if (ColorUtilKt.isGoodContrast(hintText, spacebar)) return hintText & 0x80FFFFFF; // add some transparency final int text = readUserColor(prefs, context, PREF_COLOR_TEXT_SUFFIX, isNight); if (ColorUtilKt.isGoodContrast(text, spacebar)) return text & 0x80FFFFFF; if (ColorUtilKt.isBrightColor(spacebar)) return Color.BLACK & 0x80FFFFFF; else return Color.WHITE & 0x80FFFFFF; case PREF_COLOR_BACKGROUND_SUFFIX: default: return ContextCompat.getColor(getDayNightContext(context, isNight), R.color.keyboard_background); } } public static Context getDayNightContext(final Context context, final boolean wantNight) { final boolean isNight = ResourceUtils.isNight(context.getResources()); if (isNight == wantNight) return context; final Configuration config = new Configuration(context.getResources().getConfiguration()); final int night = config.uiMode & Configuration.UI_MODE_NIGHT_MASK; final int uiModeWithNightBitsZero = config.uiMode - night; config.uiMode = uiModeWithNightBitsZero + (wantNight ? Configuration.UI_MODE_NIGHT_YES : Configuration.UI_MODE_NIGHT_NO); final ContextThemeWrapper wrapper = new ContextThemeWrapper(context, R.style.platformActivityTheme); wrapper.applyOverrideConfiguration(config); return wrapper; } public boolean isTablet() { return mContext.getResources().getInteger(R.integer.config_screen_metrics) >= 3; } public int getStringResIdByName(final String name) { return mContext.getResources().getIdentifier(name, "string", mContext.getPackageName()); } public String getInLocale(@StringRes final int resId, final Locale locale) { return RunInLocaleKt.runInLocale(mContext, locale, (ctx) -> ctx.getString(resId)); } public String readCustomCurrencyKey() { return mPrefs.getString(PREF_CUSTOM_CURRENCY_KEY, ""); } public Integer getCustomToolbarKeyCode(ToolbarKey key) { if (mCustomToolbarKeyCodes == null) mCustomToolbarKeyCodes = ToolbarUtilsKt.readCustomKeyCodes(mPrefs); return mCustomToolbarKeyCodes.get(key.name()); } public Integer getCustomToolbarLongpressCode(ToolbarKey key) { if (mCustomToolbarLongpressCodes == null) mCustomToolbarLongpressCodes = ToolbarUtilsKt.readCustomLongpressCodes(mPrefs); return mCustomToolbarLongpressCodes.get(key.name()); } public static File getCustomFontFile(final Context context) { return new File(DeviceProtectedUtils.getFilesDir(context), "custom_font"); } @Nullable public Typeface getCustomTypeface() { if (!sCustomTypefaceLoaded) { try { sCachedTypeface = Typeface.createFromFile(getCustomFontFile(mContext)); } catch (Exception e) { } } sCustomTypefaceLoaded = true; return sCachedTypeface; } public static void clearCachedTypeface() { sCachedTypeface = null; sCustomTypefaceLoaded = false; } }