Skip to content
Draft
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions app/src/main/java/helium314/keyboard/event/HapticEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package helium314.keyboard.event

import android.os.Build
import android.view.HapticFeedbackConstants

enum class HapticEvent(@JvmField val feedbackConstant: Int, @JvmField val allowCustomDuration: Boolean) {
NO_HAPTICS(HapticFeedbackConstants.NO_HAPTICS, false),
KEY_PRESS(HapticFeedbackConstants.KEYBOARD_TAP, true),
// KEY_RELEASE(
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
// HapticFeedbackConstants.KEYBOARD_RELEASE
// } else {
// HapticFeedbackConstants.?
// },
// ?
// ),
KEY_LONG_PRESS(HapticFeedbackConstants.LONG_PRESS, true),
// KEY_REPEAT(HapticFeedbackConstants.?, ?),
// GESTURE_START(
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// HapticFeedbackConstants.GESTURE_START
// } else {
// HapticFeedbackConstants.?
// },
// ?
// ),
GESTURE_MOVE(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
HapticFeedbackConstants.TEXT_HANDLE_MOVE
} else {
HapticFeedbackConstants.CLOCK_TICK
},
false
),
// GESTURE_END(
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// HapticFeedbackConstants.GESTURE_END
// } else {
// HapticFeedbackConstants.?
// },
// ?
// )
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import android.view.KeyEvent;

import helium314.keyboard.event.HapticEvent;
import helium314.keyboard.latin.common.Constants;
import helium314.keyboard.latin.common.InputPointers;

Expand All @@ -20,8 +21,10 @@ public interface KeyboardActionListener {
* the value will be zero.
* @param repeatCount how many times the key was repeated. Zero if it is the first press.
* @param isSinglePointer true if pressing has occurred while no other key is being pressed.
* @param hapticEvent the type of haptic feedback to perform.
*/
void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer);
void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer, HapticEvent hapticEvent);
void onLongPressKey();

/**
* Called when the user releases a key. This is sent after the {@link #onCodeInput} is called.
Expand Down Expand Up @@ -122,7 +125,9 @@ public interface KeyboardActionListener {

class Adapter implements KeyboardActionListener {
@Override
public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer) {}
public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer, HapticEvent hapticEvent) {}
@Override
public void onLongPressKey() {}
@Override
public void onReleaseKey(int primaryCode, boolean withSliding) {}
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.view.KeyEvent
import android.view.inputmethod.InputMethodSubtype
import helium314.keyboard.event.Event
import helium314.keyboard.event.HangulEventDecoder
import helium314.keyboard.event.HapticEvent
import helium314.keyboard.event.HardwareEventDecoder
import helium314.keyboard.event.HardwareKeyboardEventDecoder
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
Expand Down Expand Up @@ -36,6 +37,7 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp

private val keyboardSwitcher = KeyboardSwitcher.getInstance()
private val settings = Settings.getInstance()
private val audioAndHapticFeedbackManager = AudioAndHapticFeedbackManager.getInstance()
private var metaState = 0 // is this enough, or are there threading issues with the different PointerTrackers?

// language slide state
Expand All @@ -61,10 +63,15 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
else metaState or metaCode
}

override fun onPressKey(primaryCode: Int, repeatCount: Int, isSinglePointer: Boolean) {
override fun onPressKey(primaryCode: Int, repeatCount: Int, isSinglePointer: Boolean, hapticEvent: HapticEvent) {
adjustMetaState(primaryCode, false)
keyboardSwitcher.onPressKey(primaryCode, isSinglePointer, latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState)
latinIME.hapticAndAudioFeedback(primaryCode, repeatCount)
// we need to use LatinIME for handling of key-down audio and haptics
latinIME.hapticAndAudioFeedback(primaryCode, repeatCount, hapticEvent)
}

override fun onLongPressKey() {
performHapticFeedback(HapticEvent.KEY_LONG_PRESS)
}

override fun onReleaseKey(primaryCode: Int, withSliding: Boolean) {
Expand Down Expand Up @@ -110,8 +117,8 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp

override fun onCodeInput(primaryCode: Int, x: Int, y: Int, isKeyRepeat: Boolean) {
when (primaryCode) {
KeyCode.TOGGLE_AUTOCORRECT -> return Settings.getInstance().toggleAutoCorrect()
KeyCode.TOGGLE_INCOGNITO_MODE -> return Settings.getInstance().toggleAlwaysIncognitoMode()
KeyCode.TOGGLE_AUTOCORRECT -> return settings.toggleAutoCorrect()
KeyCode.TOGGLE_INCOGNITO_MODE -> return settings.toggleAlwaysIncognitoMode()
}
val mkv = keyboardSwitcher.mainKeyboardView

Expand Down Expand Up @@ -177,7 +184,7 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
}

override fun toggleNumpad(withSliding: Boolean, forceReturnToAlpha: Boolean): Boolean {
KeyboardSwitcher.getInstance().toggleNumpad(withSliding, latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState, forceReturnToAlpha)
keyboardSwitcher.toggleNumpad(withSliding, latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState, forceReturnToAlpha)
return true
}

Expand All @@ -187,7 +194,7 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
val actualSteps = actualSteps(steps)
val start = connection.expectedSelectionStart + actualSteps
if (start > end) return
AudioAndHapticFeedbackManager.getInstance().performHapticFeedback(keyboardSwitcher.visibleKeyboardView)
gestureMoveBackHaptics()
connection.setSelection(start, end)
}

Expand Down Expand Up @@ -244,21 +251,25 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
}
if (steps > 0) subtypeSwitchCount++ else subtypeSwitchCount--

KeyboardSwitcher.getInstance().switchToSubtype(newSubtype)
keyboardSwitcher.switchToSubtype(newSubtype)
return true
}

private fun onMoveCursorVertically(steps: Int): Boolean {
if (steps == 0) return false
AudioAndHapticFeedbackManager.getInstance().performHapticFeedback(keyboardSwitcher.visibleKeyboardView)
val code = if (steps < 0) KeyCode.ARROW_UP else KeyCode.ARROW_DOWN
val code = if (steps < 0) {
gestureMoveBackHaptics()
KeyCode.ARROW_UP
} else {
gestureMoveForwardHaptics()
KeyCode.ARROW_DOWN
}
onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false)
return true
}

private fun onMoveCursorHorizontally(rawSteps: Int): Boolean {
if (rawSteps == 0) return false
AudioAndHapticFeedbackManager.getInstance().performHapticFeedback(keyboardSwitcher.visibleKeyboardView)
// for RTL languages we want to invert pointer movement
val steps = if (RichInputMethodManager.getInstance().currentSubtype.isRtlSubtype) -rawSteps else rawSteps
val moveSteps: Int
Expand All @@ -273,6 +284,7 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
}
return true
}
gestureMoveBackHaptics()
} else {
val text = connection.getTextAfterCursor(steps * 4, 0) ?: return false
moveSteps = positiveMoveSteps(text, steps)
Expand All @@ -284,6 +296,7 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
}
return true
}
gestureMoveForwardHaptics()
}

// the shortcut below causes issues due to horrible handling of text fields by Firefox and forks
Expand Down Expand Up @@ -332,6 +345,22 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
return -min(-actualSteps, text.length)
}

private fun gestureMoveBackHaptics() {
if (connection.canDeleteCharacters()) {
performHapticFeedback(HapticEvent.GESTURE_MOVE)
}
}

private fun gestureMoveForwardHaptics() {
if (!connection.noTextAfterCursor()) {
performHapticFeedback(HapticEvent.GESTURE_MOVE)
}
}

private fun performHapticFeedback(hapticEvent: HapticEvent) {
audioAndHapticFeedbackManager.performHapticFeedback(keyboardSwitcher.visibleKeyboardView, hapticEvent)
}

private fun getHardwareKeyEventDecoder(deviceId: Int): HardwareEventDecoder {
hardwareEventDecoders.get(deviceId)?.let { return it }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import androidx.annotation.Nullable;
import androidx.core.util.TypedValueCompat;

import helium314.keyboard.event.HapticEvent;
import helium314.keyboard.keyboard.internal.BatchInputArbiter;
import helium314.keyboard.keyboard.internal.BatchInputArbiter.BatchInputArbiterListener;
import helium314.keyboard.keyboard.internal.BogusMoveEventDetector;
Expand Down Expand Up @@ -288,7 +289,7 @@ private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key,
return false;
}
if (key.isEnabled()) {
sListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1);
sListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1, HapticEvent.KEY_PRESS);
final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
mKeyboardLayoutHasBeenChanged = false;
sTimerProxy.startTypingStateTimer(key);
Expand Down Expand Up @@ -1128,10 +1129,11 @@ public void onLongPressed() {
if (key == null) {
return;
}
sListener.onLongPressKey();
if (key.hasNoPanelAutoPopupKey()) {
cancelKeyTracking();
final int popupKeyCode = key.getPopupKeys()[0].mCode;
sListener.onPressKey(popupKeyCode, 0, true);
sListener.onPressKey(popupKeyCode, 0, true, HapticEvent.NO_HAPTICS);
sListener.onCodeInput(popupKeyCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false);
sListener.onReleaseKey(popupKeyCode, false);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import helium314.keyboard.event.HapticEvent
import helium314.keyboard.keyboard.KeyboardActionListener
import helium314.keyboard.keyboard.KeyboardId
import helium314.keyboard.keyboard.KeyboardLayoutSet
Expand Down Expand Up @@ -197,7 +198,7 @@ class ClipboardHistoryView @JvmOverloads constructor(
override fun onClick(view: View) {
val tag = view.tag
if (tag is ToolbarKey) {
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, this)
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, this, HapticEvent.KEY_PRESS)
val code = getCodeForToolbarKey(tag)
if (code != KeyCode.UNSPECIFIED) {
keyboardActionListener.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false)
Expand All @@ -209,7 +210,7 @@ class ClipboardHistoryView @JvmOverloads constructor(
override fun onLongClick(view: View): Boolean {
val tag = view.tag
if (tag is ToolbarKey) {
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(Constants.NOT_A_CODE, this)
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, this, HapticEvent.KEY_LONG_PRESS)
val longClickCode = getCodeForToolbarKeyLongClick(tag)
if (longClickCode != KeyCode.UNSPECIFIED) {
keyboardActionListener.onCodeInput(
Expand All @@ -225,7 +226,7 @@ class ClipboardHistoryView @JvmOverloads constructor(
}

override fun onKeyDown(clipId: Long) {
keyboardActionListener.onPressKey(KeyCode.NOT_SPECIFIED, 0, true)
keyboardActionListener.onPressKey(KeyCode.NOT_SPECIFIED, 0, true, HapticEvent.KEY_PRESS)
}

override fun onKeyUp(clipId: Long) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;

import helium314.keyboard.event.HapticEvent;
import helium314.keyboard.keyboard.Key;
import helium314.keyboard.keyboard.Keyboard;
import helium314.keyboard.keyboard.KeyboardActionListener;
Expand Down Expand Up @@ -272,7 +274,7 @@ public void initialize() { // needs to be delayed for access to EmojiTabStrip, w
public void onClick(View v) {
final Object tag = v.getTag();
if (tag instanceof Long) {
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, this);
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, this, HapticEvent.KEY_PRESS);
final int categoryId = ((Long) tag).intValue();
if (categoryId != mEmojiCategory.getCurrentCategoryId()) {
setCurrentCategoryId(categoryId, false);
Expand All @@ -288,7 +290,7 @@ public void onClick(View v) {
@Override
public void onPressKey(final Key key) {
final int code = key.getCode();
mKeyboardActionListener.onPressKey(code, 0, true);
mKeyboardActionListener.onPressKey(code, 0, true, HapticEvent.KEY_PRESS);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import android.view.HapticFeedbackConstants;
import android.view.View;

import helium314.keyboard.event.HapticEvent;
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
import helium314.keyboard.latin.common.Constants;
import helium314.keyboard.latin.settings.SettingsValues;
Expand All @@ -32,7 +33,6 @@ public final class AudioAndHapticFeedbackManager {

private static final AudioAndHapticFeedbackManager sInstance =
new AudioAndHapticFeedbackManager();
private long mLastVibrationMillis;

public static AudioAndHapticFeedbackManager getInstance() {
return sInstance;
Expand All @@ -51,10 +51,13 @@ private void initInternal(final Context context) {
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
}

public void performHapticAndAudioFeedback(final int code,
final View viewToPerformHapticFeedbackOn) {
performHapticFeedback(viewToPerformHapticFeedbackOn);
performAudioFeedback(code);
public void performHapticAndAudioFeedback(
final int code,
final View viewToPerformHapticFeedbackOn,
final HapticEvent hapticEvent
) {
performHapticFeedback(viewToPerformHapticFeedbackOn, hapticEvent);
performAudioFeedback(code, hapticEvent);
}

public boolean hasVibrator() {
Expand All @@ -75,14 +78,17 @@ private boolean reevaluateIfSoundIsOn() {
return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL;
}

public void performAudioFeedback(final int code) {
public void performAudioFeedback(final int code, final HapticEvent hapticEvent) {
// if mAudioManager is null, we can't play a sound anyway, so return
if (mAudioManager == null) {
return;
}
if (!mSoundOn) {
return;
}
if (hapticEvent != HapticEvent.KEY_PRESS) {
return;
}
final int sound = switch (code) {
case KeyCode.DELETE -> AudioManager.FX_KEYPRESS_DELETE;
case Constants.CODE_ENTER -> AudioManager.FX_KEYPRESS_RETURN;
Expand All @@ -92,22 +98,22 @@ public void performAudioFeedback(final int code) {
mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume);
}

public void performHapticFeedback(final View viewToPerformHapticFeedbackOn) {
public void performHapticFeedback(final View viewToPerformHapticFeedbackOn, final HapticEvent hapticEvent) {
if (!mSettingsValues.mVibrateOn || (mDoNotDisturb && !mSettingsValues.mVibrateInDndMode)) {
return;
}

if (System.currentTimeMillis() < mLastVibrationMillis + 100) return;
mLastVibrationMillis = System.currentTimeMillis();

if (mSettingsValues.mKeypressVibrationDuration >= 0) {
if (hapticEvent == HapticEvent.NO_HAPTICS) {
// Avoid surprises with the handling of HapticFeedbackConstants.NO_HAPTICS
return;
}
if (hapticEvent.allowCustomDuration && mSettingsValues.mKeypressVibrationDuration >= 0) {
vibrate(mSettingsValues.mKeypressVibrationDuration);
return;
}
// Go ahead with the system default
if (viewToPerformHapticFeedbackOn != null) {
viewToPerformHapticFeedbackOn.performHapticFeedback(
HapticFeedbackConstants.KEYBOARD_TAP,
hapticEvent.feedbackConstant,
HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.view.inputmethod.EditorInfo
import androidx.core.view.isGone
import kotlinx.serialization.json.Json
import helium314.keyboard.compat.ClipboardManagerCompat
import helium314.keyboard.event.HapticEvent
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
import helium314.keyboard.latin.common.ColorType
import helium314.keyboard.latin.common.isValidNumber
Expand Down Expand Up @@ -206,7 +207,7 @@ class ClipboardHistoryManager(
textView.setOnClickListener {
dontShowCurrentSuggestion = true
latinIME.onTextInput(content.toString())
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, it)
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, it, HapticEvent.KEY_PRESS)
binding.root.isGone = true
}
val closeButton = binding.clipboardSuggestionClose
Expand Down
Loading
Loading