Skip to content

Commit

Permalink
Merge pull request florisboard#1142 from florisboard/input-feedback-m…
Browse files Browse the repository at this point in the history
…anager

Rework audio and haptic feedback of FlorisBoard
  • Loading branch information
patrickgold committed Aug 12, 2021
2 parents efc03a9 + 1c8523c commit 07ad682
Show file tree
Hide file tree
Showing 43 changed files with 469 additions and 336 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,7 @@ class ClipboardInputManager private constructor() : CoroutineScope by MainScope(

when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
florisboard.keyPressVibrate()
florisboard.keyPressSound(data)
florisboard.inputFeedbackManager.keyPress(data)
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.down(data))
}
MotionEvent.ACTION_UP -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import dev.patrickgold.florisboard.setup.SetupActivity
import dev.patrickgold.florisboard.util.AppVersionUtils
import dev.patrickgold.florisboard.common.ViewUtils
import dev.patrickgold.florisboard.databinding.FlorisboardBinding
import dev.patrickgold.florisboard.ime.keyboard.InputFeedbackManager
import dev.patrickgold.florisboard.ime.keyboard.KeyboardState
import dev.patrickgold.florisboard.ime.keyboard.updateKeyboardState
import dev.patrickgold.florisboard.util.debugSummarize
Expand Down Expand Up @@ -133,11 +134,10 @@ open class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardMa
private set
private var eventListeners: CopyOnWriteArrayList<EventListener> = CopyOnWriteArrayList()

private var audioManager: AudioManager? = null
var imeManager: InputMethodManager? = null
lateinit var inputFeedbackManager: InputFeedbackManager
var florisClipboardManager: FlorisClipboardManager? = null
private val themeManager: ThemeManager = ThemeManager.default()
private var vibrator: Vibrator? = null

private var internalBatchNestingLevel: Int = 0
private val internalSelectionCache = object {
Expand Down Expand Up @@ -220,9 +220,7 @@ open class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardMa
serviceLifecycleDispatcher.onServicePreSuperOnCreate()

imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
audioManager = getSystemService(Context.AUDIO_SERVICE) as? AudioManager
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
prefs.syncSystemSettings()
inputFeedbackManager = InputFeedbackManager.new(this)
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT

currentThemeIsNight = themeManager.activeTheme.isNightTheme
Expand Down Expand Up @@ -320,9 +318,7 @@ open class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardMa
it.close()
florisClipboardManager = null
}
audioManager = null
imeManager = null
vibrator = null
popupLayerView = null
uiBinding = null
florisboardInstance = null
Expand Down Expand Up @@ -490,7 +486,6 @@ open class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardMa
}
isWindowShown = true

prefs.syncSystemSettings()
val newActiveSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
if (newActiveSubtype != activeSubtype) {
activeSubtype = newActiveSubtype
Expand Down Expand Up @@ -725,84 +720,6 @@ open class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardMa
}
}

/**
* Makes a key press vibration if the user has this feature enabled in the preferences.
*/
fun keyPressVibrate(isMovingGestureEffect: Boolean = false) {
if (prefs.keyboard.vibrationEnabled) {
var vibrationDuration = prefs.keyboard.vibrationDuration.toLong()
var vibrationStrength = prefs.keyboard.vibrationStrength

if (!prefs.keyboard.vibrationEnabledSystem && vibrationDuration < 0 && vibrationStrength < 0) {
return
}

val hapticsPerformed = if (vibrationDuration < 0 && vibrationStrength < 0) {
if (isMovingGestureEffect && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
uiBinding?.inputWindowView?.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE)
} else {
uiBinding?.inputWindowView?.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
}
} else {
false
}

if (hapticsPerformed == true) {
return
}

if (vibrationDuration == -1L) {
vibrationDuration = 36
}
if (isMovingGestureEffect) {
vibrationDuration = (vibrationDuration / 8.0).toLong().coerceAtLeast(1)
}
if (vibrationStrength == -1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrationStrength = VibrationEffect.DEFAULT_AMPLITUDE
} else if (vibrationStrength == -1) {
vibrationStrength = 36
}
if (isMovingGestureEffect && vibrationStrength > 0) {
vibrationStrength = (vibrationStrength / 2.0).toInt().coerceAtLeast(1)
} else if (isMovingGestureEffect) {
vibrationStrength = 8
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator?.vibrate(
VibrationEffect.createOneShot(
vibrationDuration, vibrationStrength
)
)
} else {
@Suppress("DEPRECATION")
vibrator?.vibrate(vibrationDuration)
}
}
}

/**
* Makes a key press sound if the user has this feature enabled in the preferences.
*/
fun keyPressSound(keyData: KeyData? = null) {
if (prefs.keyboard.soundEnabled) {
val soundVolume = prefs.keyboard.soundVolume
val effect = when (keyData) {
is TextKeyData -> when (keyData.code) {
KeyCode.SPACE -> AudioManager.FX_KEYPRESS_SPACEBAR
KeyCode.DELETE -> AudioManager.FX_KEYPRESS_DELETE
KeyCode.ENTER -> AudioManager.FX_KEYPRESS_RETURN
else -> AudioManager.FX_KEYPRESS_STANDARD
}
else -> AudioManager.FX_KEYPRESS_STANDARD
}
if (soundVolume == -1 && prefs.keyboard.soundEnabledSystem) {
audioManager!!.playSoundEffect(effect)
} else if (soundVolume > 0) {
audioManager!!.playSoundEffect(effect, soundVolume / 100f)
}
}
}

/**
* Executes a given [SwipeAction]. Ignores any [SwipeAction] but the ones relevant for this
* class.
Expand Down
123 changes: 85 additions & 38 deletions app/src/main/java/dev/patrickgold/florisboard/ime/core/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package dev.patrickgold.florisboard.ime.core
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.provider.Settings
import androidx.core.os.UserManagerCompat
import androidx.preference.PreferenceManager
import dev.patrickgold.florisboard.R
Expand Down Expand Up @@ -57,6 +56,7 @@ class Preferences(
val dictionary = Dictionary(this)
val gestures = Gestures(this)
val glide = Glide(this)
val inputFeedback = InputFeedback(this)
val internal = Internal(this)
val keyboard = Keyboard(this)
val localization = Localization(this)
Expand Down Expand Up @@ -152,21 +152,6 @@ class Preferences(
}
}

/**
* Syncs the system preference values and clears the cache.
*/
fun syncSystemSettings() {
applicationContext.get()?.let { context ->
val contentResolver = context.contentResolver
keyboard.soundEnabledSystem = Settings.System.getInt(
contentResolver, Settings.System.SOUND_EFFECTS_ENABLED, 0
) != 0
keyboard.vibrationEnabledSystem = Settings.System.getInt(
contentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, 0
) != 0
}
}

/**
* Wrapper class for advanced preferences.
*/
Expand Down Expand Up @@ -329,6 +314,90 @@ class Preferences(
set(v) = prefs.setPref(PREVIEW_REFRESH_DELAY, v)
}

/**
* Wrapper class for internal preferences. A preference qualifies as an internal pref if the
* user has no ability to control this preference's value directly (via a UI pref view).
*/
class InputFeedback(private val prefs: Preferences) {
companion object {
const val AUDIO_ENABLED = "input_feedback__audio_enabled"
const val AUDIO_IGNORE_SYSTEM_SETTINGS = "input_feedback__audio_ignore_system_settings"
const val AUDIO_VOLUME = "input_feedback__audio_volume"
const val AUDIO_FEAT_KEY_PRESS = "input_feedback__audio_feat_key_press"
const val AUDIO_FEAT_KEY_LONG_PRESS = "input_feedback__audio_feat_key_long_press"
const val AUDIO_FEAT_KEY_REPEATED_ACTION = "input_feedback__audio_feat_key_repeated_action"
const val AUDIO_FEAT_GESTURE_SWIPE = "input_feedback__audio_feat_gesture_swipe"
const val AUDIO_FEAT_GESTURE_MOVING_SWIPE = "input_feedback__audio_feat_gesture_moving_swipe"

const val HAPTIC_ENABLED = "input_feedback__haptic_enabled"
const val HAPTIC_IGNORE_SYSTEM_SETTINGS = "input_feedback__haptic_ignore_system_settings"
const val HAPTIC_USE_VIBRATOR = "input_feedback__haptic_use_vibrator"
const val HAPTIC_VIBRATION_DURATION = "input_feedback__haptic_vibration_duration"
const val HAPTIC_VIBRATION_STRENGTH = "input_feedback__haptic_vibration_strength"
const val HAPTIC_FEAT_KEY_PRESS = "input_feedback__haptic_feat_key_press"
const val HAPTIC_FEAT_KEY_LONG_PRESS = "input_feedback__haptic_feat_key_long_press"
const val HAPTIC_FEAT_KEY_REPEATED_ACTION = "input_feedback__haptic_feat_key_repeated_action"
const val HAPTIC_FEAT_GESTURE_SWIPE = "input_feedback__haptic_feat_gesture_swipe"
const val HAPTIC_FEAT_GESTURE_MOVING_SWIPE = "input_feedback__haptic_feat_gesture_moving_swipe"
}

var audioEnabled: Boolean
get() = prefs.getPref(AUDIO_ENABLED, true)
set(v) = prefs.setPref(AUDIO_ENABLED, v)
var audioIgnoreSystemSettings: Boolean
get() = prefs.getPref(AUDIO_IGNORE_SYSTEM_SETTINGS, false)
set(v) = prefs.setPref(AUDIO_IGNORE_SYSTEM_SETTINGS, v)
var audioVolume: Int
get() = prefs.getPref(AUDIO_VOLUME, 50)
set(v) = prefs.setPref(AUDIO_VOLUME, v)
var audioFeatKeyPress: Boolean
get() = prefs.getPref(AUDIO_FEAT_KEY_PRESS, true)
set(v) = prefs.setPref(AUDIO_FEAT_KEY_PRESS, v)
var audioFeatKeyLongPress: Boolean
get() = prefs.getPref(AUDIO_FEAT_KEY_LONG_PRESS, false)
set(v) = prefs.setPref(AUDIO_FEAT_KEY_LONG_PRESS, v)
var audioFeatKeyRepeatedAction: Boolean
get() = prefs.getPref(AUDIO_FEAT_KEY_REPEATED_ACTION, false)
set(v) = prefs.setPref(AUDIO_FEAT_KEY_REPEATED_ACTION, v)
var audioFeatGestureSwipe: Boolean
get() = prefs.getPref(AUDIO_FEAT_GESTURE_SWIPE, false)
set(v) = prefs.setPref(AUDIO_FEAT_GESTURE_SWIPE, v)
var audioFeatGestureMovingSwipe: Boolean
get() = prefs.getPref(AUDIO_FEAT_GESTURE_MOVING_SWIPE, false)
set(v) = prefs.setPref(AUDIO_FEAT_GESTURE_MOVING_SWIPE, v)

var hapticEnabled: Boolean
get() = prefs.getPref(HAPTIC_ENABLED, true)
set(v) = prefs.setPref(HAPTIC_ENABLED, v)
var hapticIgnoreSystemSettings: Boolean
get() = prefs.getPref(HAPTIC_IGNORE_SYSTEM_SETTINGS, false)
set(v) = prefs.setPref(HAPTIC_IGNORE_SYSTEM_SETTINGS, v)
var hapticUseVibrator: Boolean
get() = prefs.getPref(HAPTIC_USE_VIBRATOR, true)
set(v) = prefs.setPref(HAPTIC_USE_VIBRATOR, v)
var hapticVibrationDuration: Int
get() = prefs.getPref(HAPTIC_VIBRATION_DURATION, 50)
set(v) = prefs.setPref(HAPTIC_VIBRATION_DURATION, v)
var hapticVibrationStrength: Int
get() = prefs.getPref(HAPTIC_VIBRATION_STRENGTH, 50)
set(v) = prefs.setPref(HAPTIC_VIBRATION_STRENGTH, v)
var hapticFeatKeyPress: Boolean
get() = prefs.getPref(HAPTIC_FEAT_KEY_PRESS, true)
set(v) = prefs.setPref(HAPTIC_FEAT_KEY_PRESS, v)
var hapticFeatKeyLongPress: Boolean
get() = prefs.getPref(HAPTIC_FEAT_KEY_LONG_PRESS, false)
set(v) = prefs.setPref(HAPTIC_FEAT_KEY_LONG_PRESS, v)
var hapticFeatKeyRepeatedAction: Boolean
get() = prefs.getPref(HAPTIC_FEAT_KEY_REPEATED_ACTION, true)
set(v) = prefs.setPref(HAPTIC_FEAT_KEY_REPEATED_ACTION, v)
var hapticFeatGestureSwipe: Boolean
get() = prefs.getPref(HAPTIC_FEAT_GESTURE_SWIPE, false)
set(v) = prefs.setPref(HAPTIC_FEAT_GESTURE_SWIPE, v)
var hapticFeatGestureMovingSwipe: Boolean
get() = prefs.getPref(HAPTIC_FEAT_GESTURE_MOVING_SWIPE, true)
set(v) = prefs.setPref(HAPTIC_FEAT_GESTURE_MOVING_SWIPE, v)
}

/**
* Wrapper class for internal preferences. A preference qualifies as an internal pref if the
* user has no ability to control this preference's value directly (via a UI pref view).
Expand Down Expand Up @@ -377,14 +446,9 @@ class Preferences(
const val ONE_HANDED_MODE = "keyboard__one_handed_mode"
const val ONE_HANDED_MODE_SCALE_FACTOR = "keyboard__one_handed_mode_scale_factor"
const val POPUP_ENABLED = "keyboard__popup_enabled"
const val SOUND_ENABLED = "keyboard__sound_enabled"
const val SOUND_VOLUME = "keyboard__sound_volume"
const val SPACE_BAR_SWITCHES_TO_CHARACTERS = "keyboard__space_bar_switches_to_characters"
const val UTILITY_KEY_ACTION = "keyboard__utility_key_action"
const val UTILITY_KEY_ENABLED = "keyboard__utility_key_enabled"
const val VIBRATION_ENABLED = "keyboard__vibration_enabled"
const val VIBRATION_DURATION = "keyboard__vibration_duration"
const val VIBRATION_STRENGTH = "keyboard__vibration_strength"
}

var bottomOffsetPortrait: Int = 0
Expand Down Expand Up @@ -438,13 +502,6 @@ class Preferences(
var popupEnabled: Boolean = false
get() = prefs.getPref(POPUP_ENABLED, true)
private set
var soundEnabled: Boolean = false
get() = prefs.getPref(SOUND_ENABLED, true)
private set
var soundEnabledSystem: Boolean = false
var soundVolume: Int = 0
get() = prefs.getPref(SOUND_VOLUME, -1)
private set
var spaceBarSwitchesToCharacters: Boolean
get() = prefs.getPref(SPACE_BAR_SWITCHES_TO_CHARACTERS, true)
set(v) = prefs.setPref(SPACE_BAR_SWITCHES_TO_CHARACTERS, v)
Expand All @@ -454,16 +511,6 @@ class Preferences(
var utilityKeyEnabled: Boolean
get() = prefs.getPref(UTILITY_KEY_ENABLED, true)
set(v) = prefs.setPref(UTILITY_KEY_ENABLED, v)
var vibrationEnabled: Boolean = false
get() = prefs.getPref(VIBRATION_ENABLED, true)
private set
var vibrationEnabledSystem: Boolean = false
var vibrationDuration: Int = 0
get() = prefs.getPref(VIBRATION_DURATION, -1)
private set
var vibrationStrength: Int = 0
get() = prefs.getPref(VIBRATION_STRENGTH, -1)
private set

fun keyHintConfiguration(): KeyHintConfiguration {
return KeyHintConfiguration(hintedSymbolsMode, hintedNumberRowMode, mergeHintPopupsEnabled)
Expand Down
Loading

0 comments on commit 07ad682

Please sign in to comment.