diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6a1d69f9..55d3de36 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -18,6 +18,7 @@
+
()
@@ -31,6 +39,10 @@ class DialpadActivity : SimpleActivity() {
private val russianCharsMap = HashMap()
private var hasRussianLocale = false
private var privateCursor: Cursor? = null
+ private var toneGeneratorHelper: ToneGeneratorHelper? = null
+ private val longPressTimeout = ViewConfiguration.getLongPressTimeout().toLong()
+ private val longPressHandler = Handler(Looper.getMainLooper())
+ private val pressedKeys = mutableSetOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -45,6 +57,8 @@ class DialpadActivity : SimpleActivity() {
speedDialValues = config.getSpeedDialValues()
privateCursor = getMyContactsCursor(false, true)
+ toneGeneratorHelper = ToneGeneratorHelper(this)
+
if (hasRussianLocale) {
initRussianChars()
dialpad_2_letters.append("\nАБВГ")
@@ -65,30 +79,19 @@ class DialpadActivity : SimpleActivity() {
}
}
- dialpad_0_holder.setOnClickListener { dialpadPressed('0', it) }
- dialpad_1_holder.setOnClickListener { dialpadPressed('1', it) }
- dialpad_2_holder.setOnClickListener { dialpadPressed('2', it) }
- dialpad_3_holder.setOnClickListener { dialpadPressed('3', it) }
- dialpad_4_holder.setOnClickListener { dialpadPressed('4', it) }
- dialpad_5_holder.setOnClickListener { dialpadPressed('5', it) }
- dialpad_6_holder.setOnClickListener { dialpadPressed('6', it) }
- dialpad_7_holder.setOnClickListener { dialpadPressed('7', it) }
- dialpad_8_holder.setOnClickListener { dialpadPressed('8', it) }
- dialpad_9_holder.setOnClickListener { dialpadPressed('9', it) }
+ setupCharClick(dialpad_1_holder, '1')
+ setupCharClick(dialpad_2_holder, '2')
+ setupCharClick(dialpad_3_holder, '3')
+ setupCharClick(dialpad_4_holder, '4')
+ setupCharClick(dialpad_5_holder, '5')
+ setupCharClick(dialpad_6_holder, '6')
+ setupCharClick(dialpad_7_holder, '7')
+ setupCharClick(dialpad_8_holder, '8')
+ setupCharClick(dialpad_9_holder, '9')
+ setupCharClick(dialpad_0_holder, '0')
+ setupCharClick(dialpad_asterisk_holder, '*', longClickable = false)
+ setupCharClick(dialpad_hashtag_holder, '#', longClickable = false)
- dialpad_1_holder.setOnLongClickListener { speedDial(1); true }
- dialpad_2_holder.setOnLongClickListener { speedDial(2); true }
- dialpad_3_holder.setOnLongClickListener { speedDial(3); true }
- dialpad_4_holder.setOnLongClickListener { speedDial(4); true }
- dialpad_5_holder.setOnLongClickListener { speedDial(5); true }
- dialpad_6_holder.setOnLongClickListener { speedDial(6); true }
- dialpad_7_holder.setOnLongClickListener { speedDial(7); true }
- dialpad_8_holder.setOnLongClickListener { speedDial(8); true }
- dialpad_9_holder.setOnLongClickListener { speedDial(9); true }
-
- dialpad_0_holder.setOnLongClickListener { dialpadPressed('+', null); true }
- dialpad_asterisk_holder.setOnClickListener { dialpadPressed('*', it) }
- dialpad_hashtag_holder.setOnClickListener { dialpadPressed('#', it) }
dialpad_clear_char.setOnClickListener { clearChar(it) }
dialpad_clear_char.setOnLongClickListener { clearInput(); true }
dialpad_call_button.setOnClickListener { initCall(dialpad_input.value, 0) }
@@ -164,12 +167,12 @@ class DialpadActivity : SimpleActivity() {
private fun dialpadPressed(char: Char, view: View?) {
dialpad_input.addCharacter(char)
- view?.performHapticFeedback()
+ maybePerformDialpadHapticFeedback(view)
}
private fun clearChar(view: View) {
dialpad_input.dispatchKeyEvent(dialpad_input.getKeyEvent(KeyEvent.KEYCODE_DEL))
- view.performHapticFeedback()
+ maybePerformDialpadHapticFeedback(view)
}
private fun clearInput() {
@@ -270,13 +273,15 @@ class DialpadActivity : SimpleActivity() {
}
}
- private fun speedDial(id: Int) {
- if (dialpad_input.value.isEmpty()) {
+ private fun speedDial(id: Int): Boolean {
+ if (dialpad_input.value.length == 1) {
val speedDial = speedDialValues.firstOrNull { it.id == id }
if (speedDial?.isValid() == true) {
initCall(speedDial.number, -1)
+ return true
}
}
+ return false
}
private fun initRussianChars() {
@@ -289,4 +294,77 @@ class DialpadActivity : SimpleActivity() {
russianCharsMap['ш'] = 8; russianCharsMap['щ'] = 8; russianCharsMap['ъ'] = 8; russianCharsMap['ы'] = 8
russianCharsMap['ь'] = 9; russianCharsMap['э'] = 9; russianCharsMap['ю'] = 9; russianCharsMap['я'] = 9
}
+
+ private fun startDialpadTone(char: Char) {
+ if (config.dialpadBeeps) {
+ pressedKeys.add(char)
+ toneGeneratorHelper?.startTone(char)
+ }
+ }
+
+ private fun stopDialpadTone(char: Char) {
+ if (config.dialpadBeeps) {
+ if (!pressedKeys.remove(char)) return
+ if (pressedKeys.isEmpty()) {
+ toneGeneratorHelper?.stopTone()
+ } else {
+ startDialpadTone(pressedKeys.last())
+ }
+ }
+ }
+
+ private fun maybePerformDialpadHapticFeedback(view: View?) {
+ if (config.dialpadVibration) {
+ view?.performHapticFeedback()
+ }
+ }
+
+ private fun performLongClick(view: View, char: Char) {
+ if (char == '0') {
+ clearChar(view)
+ dialpadPressed('+', view)
+ } else {
+ val result = speedDial(char.digitToInt())
+ if (result) {
+ stopDialpadTone(char)
+ clearChar(view)
+ }
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun setupCharClick(view: View, char: Char, longClickable: Boolean = true) {
+ view.isClickable = true
+ view.isLongClickable = true
+ view.setOnTouchListener { _, event ->
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ dialpadPressed(char, view)
+ startDialpadTone(char)
+ if (longClickable) {
+ longPressHandler.removeCallbacksAndMessages(null)
+ longPressHandler.postDelayed({
+ performLongClick(view, char)
+ }, longPressTimeout)
+ }
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ stopDialpadTone(char)
+ if (longClickable) {
+ longPressHandler.removeCallbacksAndMessages(null)
+ }
+ }
+ MotionEvent.ACTION_MOVE -> {
+ val viewContainsTouchEvent = view.boundingBox.contains(event.rawX.roundToInt(), event.rawY.roundToInt())
+ if (!viewContainsTouchEvent) {
+ stopDialpadTone(char)
+ if (longClickable) {
+ longPressHandler.removeCallbacksAndMessages(null)
+ }
+ }
+ }
+ }
+ false
+ }
+ }
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/activities/SettingsActivity.kt
index 1bc55438..f85653fb 100644
--- a/app/src/main/kotlin/com/simplemobiletools/dialer/activities/SettingsActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/dialer/activities/SettingsActivity.kt
@@ -39,6 +39,8 @@ class SettingsActivity : SimpleActivity() {
setupDialPadOpen()
setupGroupSubsequentCalls()
setupStartNameWithSurname()
+ setupDialpadVibrations()
+ setupDialpadBeeps()
setupShowCallConfirmation()
setupDisableProximitySensor()
setupDisableSwipeToAnswer()
@@ -206,6 +208,22 @@ class SettingsActivity : SimpleActivity() {
}
}
+ private fun setupDialpadVibrations() {
+ settings_dialpad_vibration.isChecked = config.dialpadVibration
+ settings_dialpad_vibration_holder.setOnClickListener {
+ settings_dialpad_vibration.toggle()
+ config.dialpadVibration = settings_dialpad_vibration.isChecked
+ }
+ }
+
+ private fun setupDialpadBeeps() {
+ settings_dialpad_beeps.isChecked = config.dialpadBeeps
+ settings_dialpad_beeps_holder.setOnClickListener {
+ settings_dialpad_beeps.toggle()
+ config.dialpadBeeps = settings_dialpad_beeps.isChecked
+ }
+ }
+
private fun setupShowCallConfirmation() {
settings_show_call_confirmation.isChecked = config.showCallConfirmation
settings_show_call_confirmation_holder.setOnClickListener {
diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/extensions/View.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/extensions/View.kt
new file mode 100644
index 00000000..60980027
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/dialer/extensions/View.kt
@@ -0,0 +1,8 @@
+package com.simplemobiletools.dialer.extensions
+
+import android.graphics.Rect
+import android.view.View
+
+val View.boundingBox
+ get() = Rect().also { getGlobalVisibleRect(it) }
+
diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallManager.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallManager.kt
index 8ff7fa19..f86f09ab 100644
--- a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallManager.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/CallManager.kt
@@ -1,9 +1,12 @@
package com.simplemobiletools.dialer.helpers
import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Handler
import android.telecom.Call
import android.telecom.InCallService
import android.telecom.VideoProfile
+import com.simplemobiletools.dialer.extensions.config
import com.simplemobiletools.dialer.extensions.getStateCompat
import com.simplemobiletools.dialer.extensions.hasCapability
import com.simplemobiletools.dialer.extensions.isConference
@@ -167,9 +170,15 @@ class CallManager {
fun getState() = getPrimaryCall()?.getStateCompat()
- fun keypad(c: Char) {
- call?.playDtmfTone(c)
- call?.stopDtmfTone()
+ fun keypad(context: Context, char: Char) {
+ call?.playDtmfTone(char)
+ if (context.config.dialpadBeeps) {
+ Handler().postDelayed({
+ call?.stopDtmfTone()
+ }, DIALPAD_TONE_LENGTH_MS)
+ } else {
+ call?.stopDtmfTone()
+ }
}
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Config.kt
index 94ec51de..070341aa 100644
--- a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Config.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Config.kt
@@ -71,4 +71,12 @@ class Config(context: Context) : BaseConfig(context) {
var wasOverlaySnackbarConfirmed: Boolean
get() = prefs.getBoolean(WAS_OVERLAY_SNACKBAR_CONFIRMED, false)
set(wasOverlaySnackbarConfirmed) = prefs.edit().putBoolean(WAS_OVERLAY_SNACKBAR_CONFIRMED, wasOverlaySnackbarConfirmed).apply()
+
+ var dialpadVibration: Boolean
+ get() = prefs.getBoolean(DIALPAD_VIBRATION, true)
+ set(dialpadVibration) = prefs.edit().putBoolean(DIALPAD_VIBRATION, dialpadVibration).apply()
+
+ var dialpadBeeps: Boolean
+ get() = prefs.getBoolean(DIALPAD_BEEPS, false)
+ set(dialpadBeeps) = prefs.edit().putBoolean(DIALPAD_BEEPS, dialpadBeeps).apply()
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Constants.kt
index fc44663c..3ed434d7 100644
--- a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Constants.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Constants.kt
@@ -1,8 +1,8 @@
package com.simplemobiletools.dialer.helpers
+import com.simplemobiletools.commons.helpers.TAB_CALL_HISTORY
import com.simplemobiletools.commons.helpers.TAB_CONTACTS
import com.simplemobiletools.commons.helpers.TAB_FAVORITES
-import com.simplemobiletools.commons.helpers.TAB_CALL_HISTORY
// shared prefs
const val SPEED_DIAL = "speed_dial"
@@ -15,7 +15,8 @@ const val SHOW_TABS = "show_tabs"
const val FAVORITES_CONTACTS_ORDER = "favorites_contacts_order"
const val FAVORITES_CUSTOM_ORDER_SELECTED = "favorites_custom_order_selected"
const val WAS_OVERLAY_SNACKBAR_CONFIRMED = "was_overlay_snackbar_confirmed"
-
+const val DIALPAD_VIBRATION = "dialpad_vibration"
+const val DIALPAD_BEEPS = "dialpad_beeps"
const val ALL_TABS_MASK = TAB_CONTACTS or TAB_FAVORITES or TAB_CALL_HISTORY
val tabsList = arrayListOf(TAB_CONTACTS, TAB_FAVORITES, TAB_CALL_HISTORY)
@@ -23,3 +24,5 @@ val tabsList = arrayListOf(TAB_CONTACTS, TAB_FAVORITES, TAB_CALL_HISTORY)
private const val PATH = "com.simplemobiletools.dialer.action."
const val ACCEPT_CALL = PATH + "accept_call"
const val DECLINE_CALL = PATH + "decline_call"
+
+const val DIALPAD_TONE_LENGTH_MS = 150L // The length of DTMF tones in milliseconds
diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/ToneGeneratorHelper.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/ToneGeneratorHelper.kt
new file mode 100644
index 00000000..f2008ecc
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/ToneGeneratorHelper.kt
@@ -0,0 +1,47 @@
+package com.simplemobiletools.dialer.helpers
+
+import android.content.Context
+import android.media.AudioManager
+import android.media.AudioManager.STREAM_DTMF
+import android.media.ToneGenerator
+
+class ToneGeneratorHelper(context: Context) {
+ private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ private val toneGenerator = ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME)
+
+ private fun isSilent(): Boolean {
+ return audioManager.ringerMode in arrayOf(AudioManager.RINGER_MODE_SILENT, AudioManager.RINGER_MODE_VIBRATE)
+ }
+
+ fun startTone(char: Char) {
+ startTone(charToTone[char] ?: -1)
+ }
+
+ private fun startTone(tone: Int) {
+ if (tone != -1 && !isSilent()) {
+ toneGenerator.startTone(tone)
+ }
+ }
+
+ fun stopTone() = toneGenerator.stopTone()
+
+ companion object {
+ const val TONE_RELATIVE_VOLUME = 80 // The DTMF tone volume relative to other sounds in the stream
+ const val DIAL_TONE_STREAM_TYPE = STREAM_DTMF
+
+ private val charToTone = HashMap().apply {
+ put('0', ToneGenerator.TONE_DTMF_0)
+ put('1', ToneGenerator.TONE_DTMF_1)
+ put('2', ToneGenerator.TONE_DTMF_2)
+ put('3', ToneGenerator.TONE_DTMF_3)
+ put('4', ToneGenerator.TONE_DTMF_4)
+ put('5', ToneGenerator.TONE_DTMF_5)
+ put('6', ToneGenerator.TONE_DTMF_6)
+ put('7', ToneGenerator.TONE_DTMF_7)
+ put('8', ToneGenerator.TONE_DTMF_8)
+ put('9', ToneGenerator.TONE_DTMF_9)
+ put('#', ToneGenerator.TONE_DTMF_P)
+ put('*', ToneGenerator.TONE_DTMF_S)
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
index 47480e83..10c7a6d0 100644
--- a/app/src/main/res/layout/activity_settings.xml
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -190,7 +190,7 @@
style="@style/SettingsHolderCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/ripple_bottom_corners">
+ android:background="@drawable/ripple_background">
+
+
+
+
+
+
+
+
+
+
+
+